/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* 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.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
namespace Assets.Oculus.VR.Editor
{
public class OVRPlatformTool : EditorWindow
{
public enum TargetPlatform
{
Rift = 0,
//OculusGoGearVR = 1, // DEPRECATED
Quest = 2,
None = 3,
};
public enum GamepadType
{
OFF,
TWINSTICK,
RIGHT_D_PAD,
LEFT_D_PAD,
};
const string urlPlatformUtil =
"https://www.oculus.com/download_app/?id=1076686279105243";
static private Process ovrPlatUtilProcess;
Vector2 commandMenuScroll;
Vector2 debugLogScroll;
static public string log;
private static bool activeProcess = false;
private static bool ranSelfUpdate = false;
private static int retryCount = 0;
private static string appToken;
private const float buttonPadding = 5.0f;
private bool showOptionalCommands = false;
private bool show2DCommands = false;
private bool showExpansionFileCommands = false;
private bool showRedistCommands = false;
private bool showUploadDebugSymbols = false;
private const float INDENT_SPACING = 15f;
private const float SINGLE_LINE_SPACING = 18f;
private const float ASSET_CONFIG_BACKGROUND_PADDING = 10f;
private const float DEFAULT_LABEL_WIDTH = 180f;
private const int MAX_DOWNLOAD_RETRY_COUNT = 2;
private static GUIStyle boldFoldoutStyle;
private static GUIStyle odhCalloutStyle;
[MenuItem("Oculus/Tools/Oculus Platform Tool")]
static void Init()
{
OVRPlatformTool.log = string.Empty;
// Get existing open window or if none, make a new one:
EditorWindow.GetWindow(typeof(OVRPlatformTool));
// Populate initial target platform value based on OVRDeviceSelector
#if UNITY_ANDROID
if (OVRDeviceSelector.isTargetDeviceQuestFamily)
{
OVRPlatformToolSettings.TargetPlatform = TargetPlatform.Quest;
}
#else
OVRPlatformToolSettings.TargetPlatform = TargetPlatform.Rift;
#endif
// Load redist packages by calling list-redists in the CLI
string dataPath = Application.dataPath;
var thread = new Thread(delegate () {
retryCount = 0;
LoadRedistPackages(dataPath);
});
thread.Start();
OVRPlugin.SendEvent("oculus_platform_tool", "show_window");
}
void OnGUI()
{
if (boldFoldoutStyle == null)
{
boldFoldoutStyle = new GUIStyle(EditorStyles.foldout);
boldFoldoutStyle.fontStyle = FontStyle.Bold;
}
if (odhCalloutStyle == null)
{
odhCalloutStyle = new GUIStyle(EditorStyles.label);
odhCalloutStyle.richText = true;
odhCalloutStyle.wordWrap = true;
}
EditorGUIUtility.labelWidth = DEFAULT_LABEL_WIDTH;
GUILayout.Label("OVR Platform Tool", EditorStyles.boldLabel);
this.titleContent.text = "OVR Platform Tool";
GUIContent TargetPlatformLabel = new GUIContent("Target Oculus Platform");
OVRPlatformToolSettings.TargetPlatform = (TargetPlatform)MakeEnumPopup(TargetPlatformLabel, OVRPlatformToolSettings.TargetPlatform);
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.None)
return;
SetOVRProjectConfig(OVRPlatformToolSettings.TargetPlatform);
SetDirtyOnGUIChange();
commandMenuScroll = EditorGUILayout.BeginScrollView(commandMenuScroll, GUILayout.Height(Screen.height / 2));
{
// Add the UI Form
EditorGUI.BeginChangeCheck();
GUILayout.Space(15.0f);
// App ID
GUIContent AppIDLabel = new GUIContent("Oculus Application ID [?]: ",
"This AppID will be used when uploading the build.");
OVRPlatformToolSettings.AppID = MakeTextBox(AppIDLabel, OVRPlatformToolSettings.AppID);
// App Token
GUIContent AppTokenLabel = new GUIContent("Oculus App Token [?]: ",
"You can get your app token from your app's Oculus API Dashboard.");
appToken = MakePasswordBox(AppTokenLabel, appToken);
// Release Channel
GUIContent ReleaseChannelLabel = new GUIContent("Release Channel [?]: ",
"Specify the releaes channel of the new build, you can reassign to other channels after upload.");
OVRPlatformToolSettings.ReleaseChannel = MakeTextBox(ReleaseChannelLabel, OVRPlatformToolSettings.ReleaseChannel);
// Releaes Note
GUIContent ReleaseNoteLabel = new GUIContent("Release Note: ");
OVRPlatformToolSettings.ReleaseNote = MakeTextBox(ReleaseNoteLabel, OVRPlatformToolSettings.ReleaseNote);
// Platform specific fields
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.Rift)
{
GUIContent BuildDirLabel = new GUIContent("Rift Build Directory [?]: ",
"The full path to the directory containing your Rift build files.");
OVRPlatformToolSettings.RiftBuildDirectory = MakeFileDirectoryField(BuildDirLabel, OVRPlatformToolSettings.RiftBuildDirectory,
"Choose Rifle Build Directory");
GUIContent BuildVersionLabel = new GUIContent("Build Version [?]: ",
"The version number shown to users.");
OVRPlatformToolSettings.RiftBuildVersion = MakeTextBox(BuildVersionLabel, OVRPlatformToolSettings.RiftBuildVersion);
GUIContent LaunchFileLabel = new GUIContent("Launch File Path [?]: ",
"The full path to the executable that launches your app.");
OVRPlatformToolSettings.RiftLaunchFile = MakeFileDirectoryField(LaunchFileLabel, OVRPlatformToolSettings.RiftLaunchFile,
"Choose Launch File", true, "exe");
}
else
{
GUIContent ApkPathLabel = new GUIContent("Build APK File Path [?]: ",
"The full path to the APK file.");
OVRPlatformToolSettings.ApkBuildPath = MakeFileDirectoryField(ApkPathLabel, OVRPlatformToolSettings.ApkBuildPath,
"Choose APK File", true, "apk");
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.Quest)
{
// Quest specific fields
}
}
GUIContent uploadDebugSymbolsLabel = new GUIContent("Upload Debug Symbols [?]: ",
"Check this box to enable uploading of debug symbols.");
OVRPlatformToolSettings.UploadDebugSymbols = MakeToggleBox(uploadDebugSymbolsLabel, OVRPlatformToolSettings.UploadDebugSymbols);
if (OVRPlatformToolSettings.UploadDebugSymbols)
{
IncrementIndent();
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.Quest)
{
if (showUploadDebugSymbols != OVRPlatformToolSettings.UploadDebugSymbols)
{
// If no debug symbol directory is set, default to the expected place based of scripting backend
if (string.IsNullOrEmpty(OVRPlatformToolSettings.DebugSymbolsDirectory))
{
ScriptingImplementation scriptingBackend = PlayerSettings.GetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup);
if (scriptingBackend == ScriptingImplementation.IL2CPP)
{
OVRPlatformToolSettings.DebugSymbolsDirectory = Path.Combine(Application.dataPath, "../Temp/StagingArea/symbols");
}
else
{
OVRPlatformToolSettings.DebugSymbolsDirectory = Path.Combine(EditorApplication.applicationContentsPath,
"PlaybackEngines/AndroidPlayer/Variations/mono/Release/Symbols/armeabi-v7a");
}
}
EditorUtility.SetDirty(OVRPlatformToolSettings.Instance);
}
GUIContent DebugSymbolLabel = new GUIContent("Debug Symbols Directory [?]: ",
"The full path to the directory containing the app symbols (libil2cpp.sym.so)");
OVRPlatformToolSettings.DebugSymbolsDirectory = MakeFileDirectoryField(DebugSymbolLabel, OVRPlatformToolSettings.DebugSymbolsDirectory,
"Choose Debug Symbols Directory", false);
GUIContent uploadSymbolsOnlyLabel = new GUIContent("Upload Debug Symbols Only [?]: ",
"Check this box to upload debug symbols to an existing build. Will require a Build ID along with the App ID, App Token, and Symbols Directory.");
OVRPlatformToolSettings.UploadDebugSymbolsOnly = MakeToggleBox(uploadSymbolsOnlyLabel, OVRPlatformToolSettings.UploadDebugSymbolsOnly);
if (OVRPlatformToolSettings.UploadDebugSymbolsOnly)
{
GUIContent BuildIDLabel = new GUIContent("Build ID [?]: ",
"This BuildID will be used for uploading debug symbols to a specific build.");
OVRPlatformToolSettings.BuildID = MakeTextBox(BuildIDLabel, OVRPlatformToolSettings.BuildID);
}
}
DecrementIndent();
}
showUploadDebugSymbols = OVRPlatformToolSettings.UploadDebugSymbols;
showOptionalCommands = EditorGUILayout.Foldout(showOptionalCommands, "Optional Commands", boldFoldoutStyle);
if (showOptionalCommands)
{
IncrementIndent();
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.Rift)
{
// Launch Parameters
GUIContent LaunchParamLabel = new GUIContent("Launch Parameters [?]: ",
"Specifies any arguments passed to the launcher.");
OVRPlatformToolSettings.RiftLaunchParams = MakeTextBox(LaunchParamLabel, OVRPlatformToolSettings.RiftLaunchParams);
GUIContent FirewallExceptionLabel = new GUIContent("Firewall Exception [?]: ",
"Specifies if a Windows Firewall exception is required.");
OVRPlatformToolSettings.RiftFirewallException = MakeToggleBox(FirewallExceptionLabel, OVRPlatformToolSettings.RiftFirewallException);
GUIContent GamepadEmulationLabel = new GUIContent("Gamepad Emulation [?]: ",
"Specifies the type of gamepad emulation used by the Oculus Touch controllers.");
OVRPlatformToolSettings.RiftGamepadEmulation = (GamepadType)MakeEnumPopup(GamepadEmulationLabel, OVRPlatformToolSettings.RiftGamepadEmulation);
show2DCommands = EditorGUILayout.Foldout(show2DCommands, "2D", boldFoldoutStyle);
if (show2DCommands)
{
IncrementIndent();
// 2D Launch File
GUIContent LaunchFile2DLabel = new GUIContent("2D Launch File [?]: ",
"The full path to the executable that launches your app in 2D mode.");
OVRPlatformToolSettings.Rift2DLaunchFile = MakeFileDirectoryField(LaunchFile2DLabel, OVRPlatformToolSettings.Rift2DLaunchFile,
"Choose 2D Launch File", true, "exe");
// 2D Launch Parameters
GUIContent LaunchParam2DLabel = new GUIContent("2D Launch Parameters [?]: ",
"Specifies any arguments passed to the launcher in 2D mode.");
OVRPlatformToolSettings.Rift2DLaunchParams = MakeTextBox(LaunchParam2DLabel, OVRPlatformToolSettings.Rift2DLaunchParams);
DecrementIndent();
}
showRedistCommands = EditorGUILayout.Foldout(showRedistCommands, "Redistributable Packages", boldFoldoutStyle);
if (showRedistCommands)
{
IncrementIndent();
for (int i = 0; i < OVRPlatformToolSettings.RiftRedistPackages.Count; i++)
{
GUIContent RedistPackageLabel = new GUIContent(OVRPlatformToolSettings.RiftRedistPackages[i].name);
OVRPlatformToolSettings.RiftRedistPackages[i].include = MakeToggleBox(RedistPackageLabel, OVRPlatformToolSettings.RiftRedistPackages[i].include);
}
DecrementIndent();
}
showExpansionFileCommands = EditorGUILayout.Foldout(showExpansionFileCommands, "Expansion Files", boldFoldoutStyle);
if (showExpansionFileCommands)
{
IncrementIndent();
// Language Pack Directory
GUIContent LanguagePackLabel = new GUIContent("Language Pack Directory [?]: ",
"The full path to the directory containing the language packs");
OVRPlatformToolSettings.LanguagePackDirectory = MakeFileDirectoryField(LanguagePackLabel, OVRPlatformToolSettings.LanguagePackDirectory,
"Choose Language Pack Directory");
}
}
else
{
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.Quest)
{
// Quest specific optional fields
}
showExpansionFileCommands = EditorGUILayout.Foldout(showExpansionFileCommands, "Expansion Files", boldFoldoutStyle);
if (showExpansionFileCommands)
{
IncrementIndent();
// OBB File Path
GUIContent ObbPathLabel = new GUIContent("OBB File Path [?]: ",
"The full path to the OBB file.");
OVRPlatformToolSettings.ObbFilePath = MakeFileDirectoryField(ObbPathLabel, OVRPlatformToolSettings.ObbFilePath,
"Choose OBB File", true, "obb");
}
}
if (showExpansionFileCommands)
{
// Assets Directory
GUIContent AssetsDirLabel = new GUIContent("Assets Directory [?]: ",
"The full path to the directory with DLCs for this build.");
string assetsDirectory = MakeFileDirectoryField(AssetsDirLabel, OVRPlatformToolSettings.AssetsDirectory,
"Choose Assets Directory");
if (assetsDirectory != OVRPlatformToolSettings.AssetsDirectory)
{
OVRPlatformToolSettings.AssetsDirectory = assetsDirectory;
OVRPlatformToolSettings.AssetConfigs.Clear();
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.AssetsDirectory))
{
DirectoryInfo dirInfo = new DirectoryInfo(OVRPlatformToolSettings.AssetsDirectory);
FileInfo[] assetFiles = dirInfo.GetFiles();
foreach (FileInfo f in assetFiles)
{
OVRPlatformToolSettings.AssetConfigs.Add(new AssetConfig(f.Name));
}
}
EditorUtility.SetDirty(OVRPlatformToolSettings.Instance);
}
// Display bordered asset configuration list
GUILayout.Space(3f);
Rect rect = GUILayoutUtility.GetRect(0, GetAssetConfigElementHeight() + (ASSET_CONFIG_BACKGROUND_PADDING * 2),
GUILayout.ExpandWidth(true));
rect.x += (EditorGUI.indentLevel * INDENT_SPACING + 5);
rect.width -= (EditorGUI.indentLevel * INDENT_SPACING + 10);
DrawAssetConfigList(rect);
DecrementIndent();
}
EditorGUI.indentLevel--;
}
if (EditorGUI.EndChangeCheck())
{
EditorUtility.SetDirty(OVRPlatformToolSettings.Instance);
}
}
EditorGUILayout.EndScrollView();
GUILayout.Space(SINGLE_LINE_SPACING);
GUILayout.FlexibleSpace();
// Run OVR Lint Option
EditorGUIUtility.labelWidth = DEFAULT_LABEL_WIDTH;
GUIContent RunOvrLintLabel = new GUIContent("Run OVR Lint (Recommended) [?]: ",
"Run OVR Lint tool to ensure project is optimized for performance and meets Oculus packaging requirement for publishing.");
OVRPlatformToolSettings.RunOvrLint = MakeToggleBox(RunOvrLintLabel, OVRPlatformToolSettings.RunOvrLint);
// Add an Upload button
GUI.enabled = !activeProcess;
GUIContent btnTxt = new GUIContent("Upload");
var rt = GUILayoutUtility.GetRect(btnTxt, GUI.skin.button, GUILayout.ExpandWidth(false));
var btnYPos = rt.center.y;
rt.center = new Vector2(EditorGUIUtility.currentViewWidth / 2 - rt.width / 2 - buttonPadding, btnYPos);
if (GUI.Button(rt, btnTxt, GUI.skin.button))
{
OVRPlugin.SendEvent("oculus_platform_tool", "upload");
OVRPlatformTool.log = string.Empty;
OnUpload(OVRPlatformToolSettings.TargetPlatform);
}
// Add a cancel button
GUI.enabled = activeProcess;
btnTxt = new GUIContent("Cancel");
rt = GUILayoutUtility.GetRect(btnTxt, GUI.skin.button, GUILayout.ExpandWidth(false));
rt.center = new Vector2(EditorGUIUtility.currentViewWidth / 2 + rt.width / 2 + buttonPadding, btnYPos);
if (GUI.Button(rt, btnTxt, GUI.skin.button))
{
if (EditorUtility.DisplayDialog("Cancel Upload Process", "Are you sure you want to cancel the upload process?", "Yes", "No"))
{
if (ovrPlatUtilProcess != null)
{
ovrPlatUtilProcess.Kill();
OVRPlatformTool.log += "Upload process was canceled\n";
}
}
}
GUI.enabled = true;
GUILayout.FlexibleSpace();
GUILayout.Space(SINGLE_LINE_SPACING);
debugLogScroll = EditorGUILayout.BeginScrollView(debugLogScroll);
GUIStyle logBoxStyle = new GUIStyle();
logBoxStyle.margin.left = 5;
logBoxStyle.wordWrap = true;
logBoxStyle.normal.textColor = logBoxStyle.focused.textColor = EditorStyles.label.normal.textColor;
EditorGUILayout.SelectableLabel(OVRPlatformTool.log, logBoxStyle, GUILayout.Height(position.height - 30));
EditorGUILayout.EndScrollView();
// ODH Callout Section
GUILayout.BeginHorizontal(EditorStyles.helpBox);
var script = MonoScript.FromScriptableObject(this);
string assetPath = AssetDatabase.GetAssetPath(script);
string editorPath = Path.GetDirectoryName(assetPath);
string odhIconPath = Path.Combine(editorPath, "Textures\\odh_icon.png");
Texture ODHIcon = (Texture)EditorGUIUtility.Load(odhIconPath);
GUILayout.Box(ODHIcon, GUILayout.Width(60.0f), GUILayout.Height(60.0f));
GUILayout.BeginVertical();
EditorGUILayout.LabelField("Oculus Developer Hub is a desktop companion tool that can upload builds, manage apps and reduce friction in daily Quest development.",
odhCalloutStyle);
GUIContent ODHLabel = new GUIContent("Download Oculus Developer Hub");
#if UNITY_2021_1_OR_NEWER
if (EditorGUILayout.LinkButton(ODHLabel))
#else
if (GUILayout.Button(ODHLabel, GUILayout.ExpandWidth(false)))
#endif
{
#if UNITY_EDITOR_WIN
Application.OpenURL("https://developer.oculus.com/downloads/package/oculus-developer-hub-win/?source=unity");
#elif UNITY_EDITOR_OSX
Application.OpenURL("https://developer.oculus.com/downloads/package/oculus-developer-hub-mac/?source=unity");
#endif
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
private void SetOVRProjectConfig(TargetPlatform targetPlatform)
{
#if UNITY_ANDROID
var targetDeviceTypes = new List();
if (targetPlatform == TargetPlatform.Quest && !OVRDeviceSelector.isTargetDeviceQuestFamily)
{
targetDeviceTypes.Add(OVRProjectConfig.DeviceType.Quest);
targetDeviceTypes.Add(OVRProjectConfig.DeviceType.Quest2);
}
if (targetDeviceTypes.Count != 0)
{
OVRProjectConfig projectConfig = OVRProjectConfig.GetProjectConfig();
projectConfig.targetDeviceTypes = targetDeviceTypes;
OVRProjectConfig.CommitProjectConfig(projectConfig);
}
#endif
}
private void IncrementIndent()
{
EditorGUI.indentLevel++;
EditorGUIUtility.labelWidth = DEFAULT_LABEL_WIDTH - (EditorGUI.indentLevel * INDENT_SPACING);
}
private void DecrementIndent()
{
EditorGUI.indentLevel--;
EditorGUIUtility.labelWidth = DEFAULT_LABEL_WIDTH - (EditorGUI.indentLevel * INDENT_SPACING);
}
private void OnUpload(TargetPlatform targetPlatform)
{
OVRPlatformTool.log = string.Empty;
SetDirtyOnGUIChange();
var lintCount = 0;
if (OVRPlatformToolSettings.RunOvrLint)
{
lintCount = OVRLint.RunCheck();
}
if (lintCount != 0)
{
OVRPlatformTool.log += lintCount.ToString() + " lint suggestions are found. \n" +
"Please run Oculus\\Tools\\OVR Performance Lint Tool to review and fix lint errors. \n" +
"You can uncheck Run OVR Lint to bypass lint errors. \n";
OVRPlugin.SendEvent("oculus_platform_tool_lint", lintCount.ToString());
}
else
{
// Continue uploading process
ExecuteCommand(targetPlatform);
}
}
static void ExecuteCommand(TargetPlatform targetPlatform)
{
string dataPath = Application.dataPath;
// If we already have a copy of the platform util, check if it needs to be updated
if (!ranSelfUpdate && File.Exists(dataPath + "/Oculus/VR/Editor/Tools/ovr-platform-util.exe"))
{
ranSelfUpdate = true;
activeProcess = true;
var updateThread = new Thread(delegate () {
retryCount = 0;
CheckForUpdate(dataPath);
});
updateThread.Start();
}
string uploadCommand;
if (genUploadCommand(targetPlatform, out uploadCommand))
{
var thread = new Thread(delegate ()
{
// Wait for update process to finish before starting upload process
while (activeProcess)
{
Thread.Sleep(100);
}
retryCount = 0;
Command(targetPlatform, dataPath, uploadCommand);
});
thread.Start();
}
else
{
UnityEngine.Debug.LogError("Failed to generated upload command.");
}
}
private static string CheckForPlatformUtil(string dataPath)
{
string toolDataPath = dataPath + "/Oculus/VR/Editor/Tools";
if (!Directory.Exists(toolDataPath))
{
Directory.CreateDirectory(toolDataPath);
}
string platformUtil = toolDataPath + "/ovr-platform-util.exe";
if (!System.IO.File.Exists(platformUtil))
{
OVRPlugin.SendEvent("oculus_platform_tool", "provision_util");
EditorCoroutine downloadCoroutine = EditorCoroutine.Start(ProvisionPlatformUtil(platformUtil));
while (!downloadCoroutine.GetCompleted()) { }
}
return platformUtil;
}
private static void InitializePlatformUtilProcess(string path, string args)
{
ovrPlatUtilProcess = new Process();
var processInfo = new ProcessStartInfo(path, args);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
ovrPlatUtilProcess.StartInfo = processInfo;
ovrPlatUtilProcess.EnableRaisingEvents = true;
}
static void CheckForUpdate(string dataPath)
{
string platformUtilPath = CheckForPlatformUtil(dataPath);
InitializePlatformUtilProcess(platformUtilPath, "self-update");
OVRPlatformTool.log += "Checking for update...\n";
ovrPlatUtilProcess.Exited += new EventHandler(
(s, e) =>
{
if (File.Exists(dataPath + ".ovr-platform-util.exe"))
{
OVRPlatformTool.log += "Cleaning up...\n";
while (File.Exists(dataPath + ".ovr-platform-util.exe")) { }
OVRPlatformTool.log += "Finished updating platform utility.\n";
}
activeProcess = false;
}
);
ovrPlatUtilProcess.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
if (e.Data != null && e.Data.Length != 0 && !e.Data.Contains("\u001b"))
{
OVRPlatformTool.log += e.Data + "\n";
}
}
);
try
{
ovrPlatUtilProcess.Start();
ovrPlatUtilProcess.BeginOutputReadLine();
}
catch
{
if (ThrowPlatformUtilStartupError(platformUtilPath))
{
CheckForUpdate(dataPath);
}
}
}
static void LoadRedistPackages(string dataPath)
{
// Check / Download the platform util and call list-redists on it
activeProcess = true;
string platformUtilPath = CheckForPlatformUtil(dataPath);
InitializePlatformUtilProcess(platformUtilPath, "list-redists");
OVRPlatformTool.log += "Loading redistributable packages...\n";
List redistPacks = new List();
ovrPlatUtilProcess.Exited += new EventHandler(
(s, e) =>
{
activeProcess = false;
}
);
ovrPlatUtilProcess.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
if (e.Data != null && e.Data.Length != 0 && !e.Data.Contains("\u001b") && !e.Data.Contains("ID"))
{
// Get the name / ID pair from the CLI and create a redist package instance
string[] terms = e.Data.Split('|');
if (terms.Length == 2)
{
RedistPackage redistPack = new RedistPackage(terms[1], terms[0]);
redistPacks.Add(redistPack);
}
}
}
);
try
{
ovrPlatUtilProcess.Start();
ovrPlatUtilProcess.BeginOutputReadLine();
ovrPlatUtilProcess.WaitForExit();
if (redistPacks.Count != OVRPlatformToolSettings.RiftRedistPackages.Count)
{
OVRPlatformTool.log += "Successfully updated redistributable packages.\n";
OVRPlatformToolSettings.RiftRedistPackages = redistPacks;
}
else
{
OVRPlatformTool.log += "Redistributable packages up to date.\n";
}
}
catch
{
if (ThrowPlatformUtilStartupError(platformUtilPath))
{
LoadRedistPackages(dataPath);
}
}
}
static void Command(TargetPlatform targetPlatform, string dataPath, string uploadCommand)
{
string platformUtilPath = CheckForPlatformUtil(dataPath);
activeProcess = true;
InitializePlatformUtilProcess(platformUtilPath, uploadCommand);
ovrPlatUtilProcess.Exited += new EventHandler(
(s, e) =>
{
activeProcess = false;
}
);
ovrPlatUtilProcess.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
if (e.Data != null && e.Data.Length != 0)
{
int index = e.Data.IndexOf("\u001b");
if (index >= 0)
{
OVRPlatformTool.log += e.Data.Substring(0, index) + "\n";
}
else
{
OVRPlatformTool.log += e.Data + "\n";
}
}
}
);
ovrPlatUtilProcess.ErrorDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
OVRPlatformTool.log += e.Data + "\n";
}
);
try
{
ovrPlatUtilProcess.Start();
ovrPlatUtilProcess.BeginOutputReadLine();
ovrPlatUtilProcess.BeginErrorReadLine();
}
catch
{
if (ThrowPlatformUtilStartupError(platformUtilPath))
{
Command(targetPlatform, dataPath, uploadCommand);
}
}
}
private static bool genUploadCommand(TargetPlatform targetPlatform, out string command)
{
bool success = true;
command = "";
if (OVRPlatformToolSettings.UploadDebugSymbols && OVRPlatformToolSettings.UploadDebugSymbolsOnly)
{
return genDebugSymbolsCommand(out command);
}
else
{
switch (targetPlatform)
{
case TargetPlatform.Rift:
command = "upload-rift-build";
break;
case TargetPlatform.Quest:
command = "upload-quest-build";
break;
default:
OVRPlatformTool.log += "ERROR: Invalid target platform selected";
success = false;
break;
}
}
// Add App ID
ValidateTextField(IDFieldValidator, OVRPlatformToolSettings.AppID, "App ID", ref success);
command += " --app-id \"" + OVRPlatformToolSettings.AppID + "\"";
// Add App Token
ValidateTextField(GenericFieldValidator, appToken, "App Token", ref success);
command += " --app-secret \"" + appToken + "\"";
// Add Platform specific fields
if (targetPlatform == TargetPlatform.Rift)
{
// Add Rift Build Directory
ValidateTextField(DirectoryValidator, OVRPlatformToolSettings.RiftBuildDirectory, "Rift Build Directory", ref success);
command += " --build-dir \"" + OVRPlatformToolSettings.RiftBuildDirectory + "\"";
// Add Rift Launch File
ValidateTextField(FileValidator, OVRPlatformToolSettings.RiftLaunchFile, "Rift Launch File Path", ref success);
command += " --launch-file \"" + OVRPlatformToolSettings.RiftLaunchFile + "\"";
// Add Rift Build Version
ValidateTextField(GenericFieldValidator, OVRPlatformToolSettings.RiftBuildVersion, "Build Version", ref success);
command += " --version \"" + OVRPlatformToolSettings.RiftBuildVersion + "\"";
// Add Rift Launch Parameters
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.RiftLaunchParams))
{
ValidateTextField(LaunchParameterValidator, OVRPlatformToolSettings.RiftLaunchParams, "Launch Parameters", ref success);
command += " --launch_params \"" + OVRPlatformToolSettings.RiftLaunchParams + "\"";
}
// Add 2D Launch File
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.Rift2DLaunchFile))
{
ValidateTextField(FileValidator, OVRPlatformToolSettings.Rift2DLaunchFile, "2D Launch File", ref success);
command += " --launch_file_2d \"" + OVRPlatformToolSettings.Rift2DLaunchFile + "\"";
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.Rift2DLaunchParams))
{
ValidateTextField(LaunchParameterValidator, OVRPlatformToolSettings.Rift2DLaunchParams, "2D Launch Parameters", ref success);
command += " --launch_params_2d \"" + OVRPlatformToolSettings.Rift2DLaunchParams + "\"";
}
}
// Add Firewall Exception
if (OVRPlatformToolSettings.RiftFirewallException)
{
command += " --firewall_exceptions true";
}
// Add Redistributable Packages
List redistCommandIds = new List();
for (int i = 0; i < OVRPlatformToolSettings.RiftRedistPackages.Count; i++)
{
if (OVRPlatformToolSettings.RiftRedistPackages[i].include)
{
redistCommandIds.Add(OVRPlatformToolSettings.RiftRedistPackages[i].id);
}
}
if (redistCommandIds.Count > 0)
{
command += " --redistributables \"" + string.Join(",", redistCommandIds.ToArray()) + "\"";
}
// Add Gamepad Emulation
if (OVRPlatformToolSettings.RiftGamepadEmulation > GamepadType.OFF &&
OVRPlatformToolSettings.RiftGamepadEmulation <= GamepadType.LEFT_D_PAD)
{
command += " --gamepad-emulation ";
switch (OVRPlatformToolSettings.RiftGamepadEmulation)
{
case GamepadType.TWINSTICK: command += "TWINSTICK"; break;
case GamepadType.RIGHT_D_PAD: command += "RIGHT_D_PAD"; break;
case GamepadType.LEFT_D_PAD: command += "LEFT_D_PAD"; break;
default: command += "OFF"; break;
}
}
// Add Rift Language Pack Directory
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.LanguagePackDirectory))
{
ValidateTextField(DirectoryValidator, OVRPlatformToolSettings.LanguagePackDirectory, "Language Pack Directory", ref success);
command += " --language_packs_dir \"" + OVRPlatformToolSettings.LanguagePackDirectory + "\"";
}
}
else
{
// Add APK Build Path
ValidateTextField(FileValidator, OVRPlatformToolSettings.ApkBuildPath, "APK Build Path", ref success);
command += " --apk \"" + OVRPlatformToolSettings.ApkBuildPath + "\"";
// Add OBB File Path
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.ObbFilePath))
{
ValidateTextField(FileValidator, OVRPlatformToolSettings.ObbFilePath, "OBB File Path", ref success);
command += " --obb \"" + OVRPlatformToolSettings.ObbFilePath + "\"";
}
// Add Debug Symbols Path
if (OVRPlatformToolSettings.UploadDebugSymbols)
{
ValidateTextField(DirectoryValidator, OVRPlatformToolSettings.DebugSymbolsDirectory, "Debug Symbols Directory", ref success);
command += " --debug-symbols-dir \"" + OVRPlatformToolSettings.DebugSymbolsDirectory + "\"";
command += " --debug-symbols-pattern \"*.sym.so\"";
}
if (OVRPlatformToolSettings.TargetPlatform == TargetPlatform.Quest)
{
// Quest specific commands
}
}
// Add Assets Directory
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.AssetsDirectory))
{
ValidateTextField(DirectoryValidator, OVRPlatformToolSettings.AssetsDirectory, "Assets Directory", ref success);
command += " --assets-dir \"" + OVRPlatformToolSettings.AssetsDirectory + "\"";
// Add Asset Configurations
if (OVRPlatformToolSettings.AssetConfigs.Count > 0)
{
List assetConfigs = new List();
for (int i = 0; i < OVRPlatformToolSettings.AssetConfigs.Count; i++)
{
List configParameters = new List();
AssetConfig config = OVRPlatformToolSettings.AssetConfigs[i];
if (config.required)
{
configParameters.Add("\\\"required\\\":true");
}
if (config.type > AssetConfig.AssetType.DEFAULT)
{
string typeCommand = "\\\"type\\\":";
switch (config.type)
{
case AssetConfig.AssetType.LANGUAGE_PACK:
configParameters.Add(typeCommand + "\\\"LANGUAGE_PACK\\\"");
break;
case AssetConfig.AssetType.STORE:
configParameters.Add(typeCommand + "\\\"STORE\\\"");
break;
default:
configParameters.Add(typeCommand + "\\\"DEFAULT\\\"");
break;
}
}
if (!string.IsNullOrEmpty(config.sku))
{
configParameters.Add("\\\"sku\\\":\\\"" + config.sku + "\\\"");
}
if (configParameters.Count > 0)
{
string configString = "\\\"" + config.name + "\\\":{" + string.Join(",", configParameters.ToArray()) + "}";
assetConfigs.Add(configString);
}
}
if (assetConfigs.Count > 0)
{
command += " --asset_files_config {" + string.Join(",", assetConfigs.ToArray()) + "}";
}
}
}
// Add Release Channel
ValidateTextField(GenericFieldValidator, OVRPlatformToolSettings.ReleaseChannel, "Release Channel", ref success);
command += " --channel \"" + OVRPlatformToolSettings.ReleaseChannel + "\"";
// Add Notes
if (!string.IsNullOrEmpty(OVRPlatformToolSettings.ReleaseNote))
{
string sanatizedReleaseNote = OVRPlatformToolSettings.ReleaseNote;
sanatizedReleaseNote = sanatizedReleaseNote.Replace("\"", "\"\"");
command += " --notes \"" + sanatizedReleaseNote + "\"";
}
return success;
}
private static bool genDebugSymbolsCommand(out string command)
{
bool success = true;
command = "upload-debug-symbols";
// Add Build ID
ValidateTextField(IDFieldValidator, OVRPlatformToolSettings.BuildID, "Build ID", ref success);
command += " --parent \"" + OVRPlatformToolSettings.BuildID + "\"";
// Add App ID
ValidateTextField(IDFieldValidator, OVRPlatformToolSettings.AppID, "App ID", ref success);
command += " --app-id \"" + OVRPlatformToolSettings.AppID + "\"";
// Add App Token
ValidateTextField(GenericFieldValidator, appToken, "App Token", ref success);
command += " --app-secret \"" + appToken + "\"";
ValidateTextField(DirectoryValidator, OVRPlatformToolSettings.DebugSymbolsDirectory, "Debug Symbols Directory", ref success);
command += " --debug-symbols-dir \"" + OVRPlatformToolSettings.DebugSymbolsDirectory + "\"";
command += " --debug-symbols-pattern \"*.sym.so\"";
return success;
}
// Private delegate for text field validation functions
private delegate TSuccess FieldValidatorDelegate(TText text, ref TError error);
// Validate the text using a given field validator function. An error message will be printed if validation fails. Success will ONLY be modified to false if validation fails.
static void ValidateTextField(FieldValidatorDelegate fieldValidator, string fieldText, string fieldName, ref bool success)
{
string error = "";
if (!fieldValidator(fieldText, ref error))
{
OVRPlatformTool.log += "ERROR: Please verify that the " + fieldName + " is correct. ";
OVRPlatformTool.log += string.IsNullOrEmpty(error) ? "\n" : error + "\n";
success = false;
}
}
// Checks if the text is null or empty
static bool GenericFieldValidator(string fieldText, ref string error)
{
if (string.IsNullOrEmpty(fieldText))
{
error = "The field is empty.";
return false;
}
return true;
}
// Checks if the App ID contains only numbers
static bool IDFieldValidator(string fieldText, ref string error)
{
if (string.IsNullOrEmpty(fieldText))
{
error = "The field is empty.";
return false;
}
else if (!Regex.IsMatch(fieldText, "^[0-9]+$"))
{
error = "The field contains invalid characters.";
return false;
}
return true;
}
// Check that the directory exists
static bool DirectoryValidator(string path, ref string error)
{
if (!Directory.Exists(path))
{
error = "The directory does not exist.";
return false;
}
return true;
}
// Check that the file exists
static bool FileValidator(string path, ref string error)
{
if (!File.Exists(path))
{
error = "The file does not exist.";
return false;
}
return true;
}
// Check if the launch parameter string contains illegal characters
static bool LaunchParameterValidator(string fieldText, ref string error)
{
if (fieldText.Contains("\""))
{
error = "The field contains illegal characters.";
return false;
}
return true;
}
void OnInspectorUpdate()
{
Repaint();
}
private static bool ThrowPlatformUtilStartupError(string utilPath)
{
if (retryCount < MAX_DOWNLOAD_RETRY_COUNT)
{
retryCount++;
OVRPlatformTool.log += "There was a problem starting Oculus Platform Util. Restarting provision process...\n";
File.Delete(utilPath);
return true;
}
else
{
OVRPlatformTool.log += "OVR Platform Tool had a problem with downloading a valid executable after several trys. Please reopen the tool to try again.\n";
return false;
}
}
private string MakeTextBox(GUIContent label, string variable)
{
string result = string.Empty;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.ExpandWidth(false));
result = EditorGUILayout.TextField(variable);
EditorGUILayout.EndHorizontal();
return result;
}
private string MakePasswordBox(GUIContent label, string variable)
{
string result = string.Empty;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.ExpandWidth(false));
result = EditorGUILayout.PasswordField(variable);
EditorGUILayout.EndHorizontal();
return result;
}
private bool MakeToggleBox(GUIContent label, bool variable)
{
bool result = false;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.ExpandWidth(false));
result = EditorGUILayout.Toggle(variable);
EditorGUILayout.EndHorizontal();
return result;
}
private Enum MakeEnumPopup(GUIContent label, Enum selected)
{
Enum result;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.ExpandWidth(false));
result = EditorGUILayout.EnumPopup(selected);
EditorGUILayout.EndHorizontal();
return result;
}
private string MakeFileDirectoryField(GUIContent label, string variable, string title, bool isFile = false, string extension = "")
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.ExpandWidth(false));
Rect rect = GUILayoutUtility.GetRect(0, SINGLE_LINE_SPACING, GUILayout.ExpandWidth(true));
EditorGUI.SelectableLabel(rect, variable);
// Create X button if there is a valid path in the field
string result = variable;
if (!string.IsNullOrEmpty(variable))
{
Color defaultColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(.9f, 0.5f, 0.5f);
rect = GUILayoutUtility.GetRect(SINGLE_LINE_SPACING, SINGLE_LINE_SPACING, GUILayout.ExpandWidth(false));
if (GUI.Button(rect, "X"))
{
result = string.Empty;
}
GUI.backgroundColor = defaultColor;
}
// Create the Choose button to initiate the file explorer
rect = GUILayoutUtility.GetRect(75f, SINGLE_LINE_SPACING, GUILayout.ExpandWidth(false));
if (GUI.Button(rect, "Choose ..."))
{
string newPath = string.Empty;
string path = string.IsNullOrEmpty(variable) ? Application.dataPath : variable;
if (isFile)
{
newPath = EditorUtility.OpenFilePanel(title, path, extension);
}
else
{
newPath = EditorUtility.OpenFolderPanel(title, path, string.Empty);
}
if (newPath.Length > 0)
{
result = newPath;
}
}
GUILayout.Space(5f);
EditorGUILayout.EndHorizontal();
// If the path has changed, deselect the selectable field so that it can update.
if (result != variable)
{
GUIUtility.hotControl = 0;
GUIUtility.keyboardControl = 0;
}
return result;
}
private static void SetDirtyOnGUIChange()
{
if (GUI.changed)
{
EditorUtility.SetDirty(OVRPlatformToolSettings.Instance);
GUI.changed = false;
}
}
private static IEnumerator ProvisionPlatformUtil(string dataPath)
{
UnityEngine.Debug.Log("Started Provisioning Oculus Platform Util");
var webRequest = new UnityWebRequest(urlPlatformUtil, UnityWebRequest.kHttpVerbGET);
string path = dataPath;
webRequest.downloadHandler = new DownloadHandlerFile(path);
// WWW request timeout in seconds
webRequest.timeout = 60;
UnityWebRequestAsyncOperation webOp = webRequest.SendWebRequest();
while (!webOp.isDone) { }
#if UNITY_2020_1_OR_NEWER
if (webRequest.result == UnityWebRequest.Result.ConnectionError ||
webRequest.result == UnityWebRequest.Result.ProtocolError)
#else
if (webRequest.isNetworkError || webRequest.isHttpError)
#endif
{
var networkErrorMsg = "Failed to provision Oculus Platform Util\n";
UnityEngine.Debug.LogError(networkErrorMsg);
OVRPlatformTool.log += networkErrorMsg;
}
else
{
OVRPlatformTool.log += "Completed Provisioning Oculus Platform Util\n";
}
SetDirtyOnGUIChange();
yield return webOp;
}
private static void DrawAssetConfigList(Rect rect)
{
DrawAssetConfigHeader(rect);
DrawAssetConfigBackground(rect);
DrawAssetConfigElement(rect);
}
private static void DrawAssetConfigElement(Rect rect)
{
Rect elementRect = new Rect(rect.x, rect.y + SINGLE_LINE_SPACING + ASSET_CONFIG_BACKGROUND_PADDING / 2,
rect.width, SINGLE_LINE_SPACING);
if (OVRPlatformToolSettings.AssetConfigs.Count > 0)
{
for (int i = 0; i < OVRPlatformToolSettings.AssetConfigs.Count; i++)
{
AssetConfig config = OVRPlatformToolSettings.AssetConfigs[i];
GUIContent fieldLabel;
config.SetFoldoutState(EditorGUI.Foldout(elementRect, config.GetFoldoutState(), config.name, boldFoldoutStyle));
if (config.GetFoldoutState())
{
Rect attributeRect = new Rect(elementRect.x + INDENT_SPACING, elementRect.y + SINGLE_LINE_SPACING,
elementRect.width - INDENT_SPACING - 3f, SINGLE_LINE_SPACING);
// Extra asset config params are disabled for now until CLI supports them
#if !DISABLE_EXTRA_ASSET_CONFIG
fieldLabel = new GUIContent("Required Asset [?]", "Whether or not this asset file is required for the app to run.");
config.required = EditorGUI.Toggle(attributeRect, fieldLabel, config.required);
attributeRect.y += SINGLE_LINE_SPACING;
fieldLabel = new GUIContent("Asset Type [?]", "The asset file type.");
config.type = (AssetConfig.AssetType)EditorGUI.EnumPopup(attributeRect, fieldLabel, config.type);
attributeRect.y += SINGLE_LINE_SPACING;
#endif
fieldLabel = new GUIContent("Asset SKU [?]", "The Oculus store SKU for this asset file.");
config.sku = EditorGUI.TextField(attributeRect, fieldLabel, config.sku);
elementRect.y = attributeRect.y;
}
elementRect.y += SINGLE_LINE_SPACING;
}
}
else
{
EditorGUI.LabelField(elementRect, "No asset files found. Choose a valid assets directory.");
}
}
private static float GetAssetConfigElementHeight()
{
float totalHeight = 0f;
if (OVRPlatformToolSettings.AssetConfigs.Count > 0)
{
for (int i = 0; i < OVRPlatformToolSettings.AssetConfigs.Count; i++)
{
AssetConfig config = OVRPlatformToolSettings.AssetConfigs[i];
#if !DISABLE_EXTRA_ASSET_CONFIG
totalHeight += config.GetFoldoutState() ? SINGLE_LINE_SPACING * 4 : SINGLE_LINE_SPACING;
#else
totalHeight += config.GetFoldoutState() ? SINGLE_LINE_SPACING * 2 : SINGLE_LINE_SPACING;
#endif
}
}
else
{
totalHeight += SINGLE_LINE_SPACING;
}
return totalHeight + ASSET_CONFIG_BACKGROUND_PADDING;
}
private static void DrawAssetConfigHeader(Rect rect)
{
Rect headerRect = new Rect(rect.x, rect.y, rect.width, SINGLE_LINE_SPACING);
EditorGUI.DrawRect(headerRect, EditorGUIUtility.isProSkin ? new Color(0.37f, 0.37f, 0.37f) : new Color(0.55f, 0.55f, 0.55f));
EditorGUI.LabelField(rect, "Asset File Configuration");
}
private static void DrawAssetConfigBackground(Rect rect)
{
Rect backgroundRect = new Rect(rect.x, rect.y + SINGLE_LINE_SPACING, rect.width, GetAssetConfigElementHeight());
EditorGUI.DrawRect(backgroundRect, EditorGUIUtility.isProSkin ? new Color(0.3f, 0.3f, 0.3f) : new Color(0.63f, 0.63f, 0.63f));
}
class GUIHelper
{
public delegate void Worker();
static void InOut(Worker begin, Worker body, Worker end)
{
try
{
begin();
body();
}
finally
{
end();
}
}
public static void HInset(int pixels, Worker worker)
{
InOut(
() => {
GUILayout.BeginHorizontal();
GUILayout.Space(pixels);
GUILayout.BeginVertical();
},
worker,
() => {
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
);
}
public delegate T ControlWorker();
public static T MakeControlWithLabel(GUIContent label, ControlWorker worker)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label);
var result = worker();
EditorGUILayout.EndHorizontal();
return result;
}
}
public class EditorCoroutine
{
public static EditorCoroutine Start(IEnumerator routine)
{
EditorCoroutine coroutine = new EditorCoroutine(routine);
coroutine.Start();
return coroutine;
}
readonly IEnumerator routine;
bool completed;
EditorCoroutine(IEnumerator _routine)
{
routine = _routine;
completed = false;
}
void Start()
{
EditorApplication.update += Update;
}
public void Stop()
{
EditorApplication.update -= Update;
completed = true;
}
public bool GetCompleted()
{
return completed;
}
void Update()
{
if (!routine.MoveNext())
{
Stop();
}
}
}
}
}