Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36c0d886a26373c46be857f2fc441071
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VRC.SDKBase
{
public class AudioManagerSettings
{
public const float MinVoiceSendDistance = 25.0f; // meters
public const float MaxVoiceSendDistancePctOfFarRange = 0.5f;
public const float VoiceFadeOutDistancePctOfFarRange = 0.25f;
public const float RoomAudioGain = 10f; // dB
public const float RoomAudioMaxRange = 80f; // meters
public const float VoiceGain = 15f; // dB
public const float VoiceMaxRange = 25f; // meters, this is half the oculus inv sq max range
public const float LipsyncGain = 1f; // multiplier, not dB!
public const float AvatarAudioMaxGain = 10f; // dB
public const float AvatarAudioMaxRange = 40f; // meters
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: acadc6659c5ab3446ad0d5de2563f95f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
using UnityEngine;
namespace VRCSDK2
{
public class CommunityLabsConstants
{
public const string COMMUNITY_LABS_DOCUMENTATION_URL = "https://docs.vrchat.com/docs/vrchat-community-labs";
public const string MANAGE_WORLD_IN_BROWSER_STRING = "Manage World in Browser";
public const string READ_COMMUNITY_LABS_DOCS_STRING = "Read Community Labs Docs";
public const string UPLOADED_CONTENT_SUCCESSFULLY_MESSAGE = "Content Successfully Uploaded!";
public const string UPLOADED_NEW_PRIVATE_WORLD_CONFIRMATION_MESSAGE = "You've uploaded a private world. You can find it on the \"Mine\" row in your worlds menu.";
public const string UPDATED_PRIVATE_WORLD_CONFIRMATION_MESSAGE = "You've updated your private world. You can find it on the \"Mine\" row in your worlds menu.";
public const string PUBLISHED_WORLD_TO_COMMUNITY_LABS_CONFIRMATION_MESSAGE = "You've uploaded and published a world to Community Labs.";
public const string UPDATED_COMMUNITY_LABS_WORLD_CONFIRMATION_MESSAGE = "You've updated your Community Labs world.";
public const string UPDATED_PUBLIC_WORLD_CONFIRMATION_MESSAGE = "You've updated your public world.";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d047eaa3325d654aa62ccad6f73eb93
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,49 @@
using System;
using System.Reflection;
namespace VRC. SDKBase
{
public static class GameViewMethods
{
private static readonly Type GameViewType = System.Type.GetType("UnityEditor.GameView,UnityEditor");
private static readonly Type PlayModeViewType = System.Type.GetType("UnityEditor.PlayModeView, UnityEditor");
public static int GetSelectedSizeIndex()
{
return (int) GetSelectedSizeProperty().GetValue(GetPlayModeViewObject());
}
public static void SetSelectedSizeIndex(int value)
{
var selectedSizeIndexProp = GetSelectedSizeProperty();
selectedSizeIndexProp.SetValue(GetPlayModeViewObject(), value, null);
}
// Set it to something else just to force a refresh
public static void ResizeGameView()
{
int current = GetSelectedSizeIndex();
SetSelectedSizeIndex(current == 0 ? 1 : 0);
}
private static PropertyInfo GetSelectedSizeProperty()
{
return GameViewType.GetProperty("selectedSizeIndex",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
private static Object GetPlayModeViewObject()
{
MethodInfo GetMainPlayModeView = PlayModeViewType.GetMethod("GetMainPlayModeView",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
return GetMainPlayModeView.Invoke(null, null);
}
public static void Repaint()
{
MethodInfo RepaintAll = PlayModeViewType.GetMethod("RepaintAll", BindingFlags.NonPublic | BindingFlags.Static);
RepaintAll.Invoke(GetPlayModeViewObject(), null);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d0c461e358764cd1ab95544e34b0346c
timeCreated: 1627603592

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c0f7ad406aeb6f64ab5c36b429a466b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class FallbackMaterialCache
{
private readonly Dictionary<Material, Material> _fallbackMaterialCache = new Dictionary<Material, Material>();
public void AddFallbackMaterial(Material material, Material fallbackMaterial)
{
if(!_fallbackMaterialCache.ContainsKey(material))
{
_fallbackMaterialCache.Add(material, fallbackMaterial);
}
else
{
#pragma warning disable RS0030 // Banned APIs
Debug.LogError($"Attempted to add a duplicate fallback material '{fallbackMaterial.name}' for original material '{material.name}'.");
#pragma warning restore RS0030
}
}
public bool TryGetFallbackMaterial(Material material, out Material fallbackMaterial)
{
if(material != null)
{
return _fallbackMaterialCache.TryGetValue(material, out fallbackMaterial);
}
fallbackMaterial = null;
return false;
}
public void Clear()
{
Material[] cachedFallbackMaterials = _fallbackMaterialCache.Values.ToArray();
for(int i = cachedFallbackMaterials.Length - 1; i >= 0; i--)
{
if (Application.isPlaying)
{
Object.Destroy(cachedFallbackMaterials[i]);
}
else
{
Object.DestroyImmediate(cachedFallbackMaterials[i]);
}
}
_fallbackMaterialCache.Clear();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10121679f780956408f9a434a526f553
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,325 @@
using System.Collections;
using System.Collections.Generic;
using System;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using VRC.Core;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VRCSDK2
{
#if UNITY_EDITOR
[Obsolete("Runtime uploads are deprecated. Use methods provided by the VRC.SDKBase.Editor.Api.VRCApi class for uploads")]
public class RuntimeAPICreation : MonoBehaviour
{
public VRC.Core.PipelineManager pipelineManager;
protected bool forceNewFileCreation = false;
protected bool useFileApi = false;
protected bool isUploading = false;
protected float uploadProgress = 0f;
protected string uploadMessage;
protected string uploadTitle;
protected string uploadVrcPath;
protected string uploadUnityPackagePath;
protected string cloudFrontAssetUrl;
protected string cloudFrontImageUrl;
protected string cloudFrontUnityPackageUrl;
protected CameraImageCapture imageCapture;
private bool cancelRequested = false;
public static bool publishingToCommunityLabs = false;
private Dictionary<string, string> mRetryState = new Dictionary<string, string>();
protected bool isUpdate { get { return pipelineManager.completedSDKPipeline; } }
protected void Start()
{
if (!Application.isEditor || !Application.isPlaying)
return;
PipelineSaver ps = GameObject.FindFirstObjectByType<PipelineSaver>();
pipelineManager = ps.gameObject.GetComponent<PipelineManager>();
imageCapture = GetComponent<CameraImageCapture>();
imageCapture.shotCamera = GameObject.Find("VRCCam").GetComponent<Camera>();
LoadUploadRetryStateFromCache();
forceNewFileCreation = UnityEditor.EditorPrefs.GetBool("forceNewFileCreation", true);
useFileApi = UnityEditor.EditorPrefs.GetBool("useFileApi", false);
API.SetOnlineMode(true);
}
protected void Update()
{
if (isUploading)
{
bool cancelled = UnityEditor.EditorUtility.DisplayCancelableProgressBar(uploadTitle, uploadMessage, uploadProgress);
if (cancelled)
{
cancelRequested = true;
}
if (EditorApplication.isPaused)
EditorApplication.isPaused = false;
}
}
protected void LoadUploadRetryStateFromCache()
{
try
{
string json = File.ReadAllText(GetUploadRetryStateFilePath());
mRetryState = VRC.Tools.ObjDictToStringDict(VRC.Tools.JsonDecode(json) as Dictionary<string, object>);
Debug.LogFormat("<color=yellow> loaded retry state: {0}</color>", json);
}
catch (Exception)
{
// normal case
return;
}
Debug.Log("Loaded upload retry state from: " + GetUploadRetryStateFilePath());
}
protected void SaveUploadRetryState(string key, string val)
{
if (string.IsNullOrEmpty(val))
return;
mRetryState[key] = val;
SaveUploadRetryState();
}
protected void SaveUploadRetryState()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(GetUploadRetryStateFilePath()));
string json = VRC.Tools.JsonEncode(mRetryState);
File.WriteAllText(GetUploadRetryStateFilePath(), json);
Debug.LogFormat("<color=yellow> wrote retry state: {0}</color>", json);
}
catch (Exception e)
{
Debug.LogError("Couldn't save upload retry state: " + GetUploadRetryStateFilePath() + "\n" + e.Message);
return;
}
Debug.Log("Saved upload retry state to: " + GetUploadRetryStateFilePath());
}
protected void ClearUploadRetryState()
{
try
{
if (!File.Exists(GetUploadRetryStateFilePath()))
return;
File.Delete(GetUploadRetryStateFilePath());
}
catch (Exception e)
{
Debug.LogError("Couldn't delete upload retry state: " + GetUploadRetryStateFilePath() + "\n" + e.Message);
return;
}
Debug.Log("Cleared upload retry state at: " + GetUploadRetryStateFilePath());
}
protected string GetUploadRetryStateFilePath()
{
string id = UnityEditor.AssetDatabase.AssetPathToGUID(SceneManager.GetActiveScene().path);
return Path.Combine(VRC.Tools.GetTempFolderPath(id), "upload_retry.dat");
}
protected string GetUploadRetryStateValue(string key)
{
return mRetryState.ContainsKey(key) ? mRetryState[key] : "";
}
protected virtual void DisplayUpdateCompletedDialog(string contentUrl=null)
{
if (UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Update Complete! Launch VRChat to see your uploaded content." + (null==contentUrl ? "" : "\n\nManage content at: " + contentUrl ), (null == contentUrl) ? "Okay" : CommunityLabsConstants.MANAGE_WORLD_IN_BROWSER_STRING, (null == contentUrl) ? "" : "Done" ))
{
if (null!=contentUrl)
{
Application.OpenURL(contentUrl);
}
}
}
protected void OnSDKPipelineComplete(string contentUrl=null)
{
VRC.Core.Logger.Log("OnSDKPipelineComplete", API.LOG_CATEGORY);
isUploading = false;
pipelineManager.completedSDKPipeline = true;
ClearUploadRetryState();
UnityEditor.EditorPrefs.SetBool("forceNewFileCreation", false);
UnityEditor.EditorApplication.isPaused = false;
UnityEditor.EditorApplication.isPlaying = false;
UnityEditor.EditorUtility.ClearProgressBar();
DisplayUpdateCompletedDialog(contentUrl);
}
protected void OnSDKPipelineError(string error, string details)
{
VRC.Core.Logger.Log("OnSDKPipelineError: " + error + " - " + details, API.LOG_CATEGORY);
isUploading = false;
pipelineManager.completedSDKPipeline = true;
UnityEditor.EditorApplication.isPaused = false;
UnityEditor.EditorApplication.isPlaying = false;
UnityEditor.EditorUtility.ClearProgressBar();
if (cancelRequested)
UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "The update was cancelled.", "Okay");
else
UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Error updating content. " + error + "\n" + details, "Okay");
}
protected void SetUploadProgress(string title, string message, float progress)
{
uploadTitle = title;
uploadMessage = message;
uploadProgress = progress;
}
protected bool WasCancelRequested(ApiFile apiFile)
{
return cancelRequested;
}
protected void PrepareUnityPackageForS3(string packagePath, string blueprintId, int version, AssetVersion assetVersion)
{
uploadUnityPackagePath = Application.temporaryCachePath + "/" + blueprintId + "_" + version.ToString() + "_" + Application.unityVersion + "_" + assetVersion.ApiVersion + "_" + VRC.Tools.Platform +
"_" + API.GetServerEnvironmentForApiUrl() + ".unitypackage";
uploadUnityPackagePath.Trim();
uploadUnityPackagePath.Replace(' ', '_');
if (System.IO.File.Exists(uploadUnityPackagePath))
System.IO.File.Delete(uploadUnityPackagePath);
System.IO.File.Copy(packagePath, uploadUnityPackagePath);
}
protected void PrepareVRCPathForS3(string abPath, string blueprintId, int version, AssetVersion assetVersion)
{
uploadVrcPath = Application.temporaryCachePath + "/" + blueprintId + "_" + version.ToString() + "_" + Application.unityVersion + "_" + assetVersion.ApiVersion + "_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl() + System.IO.Path.GetExtension(abPath);
uploadVrcPath.Trim();
uploadVrcPath.Replace(' ', '_');
if (System.IO.File.Exists(uploadVrcPath))
System.IO.File.Delete(uploadVrcPath);
System.IO.File.Copy(abPath, uploadVrcPath);
}
protected IEnumerator UploadFile(string filename, string existingFileUrl, string friendlyFilename, string fileType, Action<string> onSuccess)
{
if (string.IsNullOrEmpty(filename))
yield break;
VRC.Core.Logger.Log("Uploading " + fileType + "(" + filename + ") ...", API.LOG_CATEGORY);
SetUploadProgress("Uploading " + fileType + "...", "", 0.0f);
string fileId = GetUploadRetryStateValue(filename);
if (string.IsNullOrEmpty(fileId))
fileId = isUpdate ? ApiFile.ParseFileIdFromFileAPIUrl(existingFileUrl) : "";
string errorStr = "";
string newFileUrl = "";
yield return StartCoroutine(ApiFileHelper.Instance.UploadFile(filename, forceNewFileCreation ? "" : fileId, friendlyFilename,
delegate (ApiFile apiFile, string message)
{
newFileUrl = apiFile.GetFileURL();
if (VRC.Core.Logger.CategoryIsEnabled(API.LOG_CATEGORY))
VRC.Core.Logger.Log(fileType + " upload succeeded: " + message + " (" + filename +
") => " + apiFile.ToString(), API.LOG_CATEGORY);
else
VRC.Core.Logger.Log(fileType + " upload succeeded ");
},
delegate (ApiFile apiFile, string error)
{
SaveUploadRetryState(filename, apiFile.id);
errorStr = error;
Debug.LogError(fileType + " upload failed: " + error + " (" + filename +
") => " + apiFile.ToString());
},
delegate (ApiFile apiFile, string status, string subStatus, float pct)
{
SetUploadProgress("Uploading " + fileType + "...", status + (!string.IsNullOrEmpty(subStatus) ? " (" + subStatus + ")" : ""), pct);
},
WasCancelRequested
));
if (!string.IsNullOrEmpty(errorStr))
{
OnSDKPipelineError(fileType + " upload failed.", errorStr);
yield break;
}
if (onSuccess != null)
onSuccess(newFileUrl);
}
protected IEnumerator UpdateImage(string existingFileUrl, string friendlyFileName)
{
string imagePath = imageCapture.TakePicture();
if (!string.IsNullOrEmpty(imagePath))
{
yield return StartCoroutine(UploadFile(imagePath, existingFileUrl, friendlyFileName, "Image",
delegate (string fileUrl)
{
cloudFrontImageUrl = fileUrl;
}
));
}
}
protected virtual IEnumerator CreateBlueprint()
{
throw new NotImplementedException();
}
protected virtual IEnumerator UpdateBlueprint()
{
throw new NotImplementedException();
}
protected bool ValidateNameInput(InputField nameInput)
{
bool isValid = true;
if (string.IsNullOrEmpty(nameInput.text))
{
isUploading = false;
UnityEditor.EditorUtility.DisplayDialog("Invalid Input", "Cannot leave the name field empty.", "OK");
isValid = false;
}
return isValid;
}
protected bool ValidateAssetBundleBlueprintID(string blueprintID)
{
string lastBuiltID = UnityEditor.EditorPrefs.GetString("lastBuiltAssetBundleBlueprintID", "");
return !string.IsNullOrEmpty(lastBuiltID) && lastBuiltID == blueprintID;
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3132e0ab7e16494a9d492087a1ca447
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,421 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using VRC.Core;
using VRC.SDKBase;
using VRC.SDK3.Image;
namespace VRCSDK2
{
#if UNITY_EDITOR
[Obsolete("Runtime uploads are deprecated. Use methods provided by the VRC.SDKBase.Editor.Api.VRCApi class for uploads")]
public class RuntimeBlueprintCreation : RuntimeAPICreation
{
public GameObject waitingPanel;
public GameObject blueprintPanel;
public GameObject errorPanel;
public Text titleText;
public InputField blueprintName;
public InputField blueprintDescription;
public RawImage bpImage;
public Image liveBpImage;
public Toggle shouldUpdateImageToggle;
public Toggle contentSex;
public Toggle contentViolence;
public Toggle contentGore;
public Toggle contentOther;
public Toggle developerAvatar;
public Toggle sharePrivate;
public Toggle sharePublic;
public Toggle tagFallback;
public UnityEngine.UI.Button uploadButton;
private ApiAvatar apiAvatar;
new void Start()
{
if (!Application.isEditor || !Application.isPlaying)
return;
base.Start();
var desc = pipelineManager.GetComponent<VRC.SDKBase.VRC_AvatarDescriptor>();
desc.PositionPortraitCamera(imageCapture.shotCamera.transform);
Application.runInBackground = true;
UnityEngine.XR.XRSettings.enabled = false;
uploadButton.onClick.AddListener(SetupUpload);
shouldUpdateImageToggle.onValueChanged.AddListener(ToggleUpdateImage);
Login();
}
void LoginErrorCallback(string obj)
{
VRC.Core.Logger.LogError("Could not log in - " + obj);
blueprintPanel.SetActive(false);
errorPanel.SetActive(true);
}
void Login()
{
if (!ApiCredentials.Load())
LoginErrorCallback("Not logged in");
else
APIUser.InitialFetchCurrentUser(
delegate (ApiModelContainer<APIUser> c)
{
pipelineManager.user = c.Model as APIUser;
ApiAvatar av = new ApiAvatar() { id = pipelineManager.blueprintId };
av.Get(false,
(c2) =>
{
VRC.Core.Logger.Log("<color=magenta>Updating an existing avatar.</color>", API.LOG_CATEGORY);
apiAvatar = c2.Model as ApiAvatar;
pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(apiAvatar.authorId);
SetupUI();
},
(c2) =>
{
VRC.Core.Logger.Log("<color=magenta>Creating a new avatar.</color>", API.LOG_CATEGORY);
apiAvatar = new ApiAvatar();
apiAvatar.id = pipelineManager.blueprintId;
pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(apiAvatar.authorId);
SetupUI();
});
}, (c) => {
LoginErrorCallback(c.Error);
});
}
void SetupUI()
{
if (!ValidateAssetBundleBlueprintID(apiAvatar.id))
{
blueprintPanel.SetActive(false);
errorPanel.SetActive(true);
OnSDKPipelineError("The asset bundle is out of date. Please rebuild the scene using 'New Build'.", "The blueprint ID in the scene does not match the id in the asset bundle.");
return;
}
if (APIUser.Exists(pipelineManager.user))
{
waitingPanel.SetActive(false);
blueprintPanel.SetActive(true);
errorPanel.SetActive(false);
if (isUpdate)
{
// bp update
if (apiAvatar.authorId == pipelineManager.user.id)
{
titleText.text = "Update Avatar";
// apiAvatar = pipelineManager.user.GetBlueprint(pipelineManager.blueprintId) as ApiAvatar;
blueprintName.text = apiAvatar.name;
contentSex.isOn = apiAvatar.tags.Contains("content_sex");
contentViolence.isOn = apiAvatar.tags.Contains("content_violence");
contentGore.isOn = apiAvatar.tags.Contains("content_gore");
contentOther.isOn = apiAvatar.tags.Contains("content_other");
developerAvatar.isOn = apiAvatar.tags.Contains("developer");
sharePrivate.isOn = apiAvatar.releaseStatus.Contains("private");
sharePublic.isOn = apiAvatar.releaseStatus.Contains("public");
tagFallback.isOn = apiAvatar.tags.Contains("author_quest_fallback");
tagFallback.transform.parent.gameObject.SetActive(true);
switch (pipelineManager.fallbackStatus)
{
case PipelineManager.FallbackStatus.Valid:
#if UNITY_ANDROID || UNITY_IOS
tagFallback.interactable = true;
tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback";
#else
tagFallback.interactable = false;
tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (change only with Android upload)";
#endif
break;
case PipelineManager.FallbackStatus.InvalidPerformance:
case PipelineManager.FallbackStatus.InvalidRig:
tagFallback.isOn = false; // need to remove tag on this upload, the updated version is not up-to-spec
tagFallback.interactable = false;
tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (avatar not valid, tag will be cleared)";
break;
}
blueprintDescription.text = apiAvatar.description;
shouldUpdateImageToggle.interactable = true;
shouldUpdateImageToggle.isOn = false;
liveBpImage.enabled = false;
bpImage.enabled = true;
ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, (Texture2D obj) => bpImage.texture = obj, null);
}
else // user does not own apiAvatar id associated with descriptor
{
Debug.LogErrorFormat("{0} is not an owner of {1}", apiAvatar.authorId, pipelineManager.user.id);
blueprintPanel.SetActive(false);
errorPanel.SetActive(true);
}
}
else
{
titleText.text = "New Avatar";
shouldUpdateImageToggle.interactable = false;
shouldUpdateImageToggle.isOn = true;
liveBpImage.enabled = true;
bpImage.enabled = false;
tagFallback.isOn = false;
// Janky fix for an avatar's blueprint image not showing up the very first time you press publish in a project until you resize the window
// can remove if we fix the underlying issue or move publishing out of Play Mode
string firstTimeResize = $"{Application.identifier}-firstTimeResize";
if (!PlayerPrefs.HasKey(firstTimeResize))
{
GameViewMethods.ResizeGameView();
PlayerPrefs.SetInt(firstTimeResize, 1);
}
tagFallback.transform.parent.gameObject.SetActive(true);
switch (pipelineManager.fallbackStatus)
{
case PipelineManager.FallbackStatus.Valid:
#if UNITY_ANDROID || UNITY_IOS
tagFallback.interactable = true;
tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback";
#else
tagFallback.interactable = false;
tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (change only with Android upload)";
#endif
break;
case PipelineManager.FallbackStatus.InvalidPerformance:
case PipelineManager.FallbackStatus.InvalidRig:
tagFallback.transform.parent.gameObject.SetActive(true);
tagFallback.interactable = false;
tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (avatar not valid, tag will be cleared)";
break;
}
}
}
else
{
waitingPanel.SetActive(true);
blueprintPanel.SetActive(false);
errorPanel.SetActive(false);
}
if (APIUser.CurrentUser != null && APIUser.CurrentUser.hasSuperPowers)
developerAvatar.gameObject.SetActive(true);
else
developerAvatar.gameObject.SetActive(false);
}
public void SetupUpload()
{
uploadTitle = "Preparing For Upload";
isUploading = true;
string abPath = UnityEditor.EditorPrefs.GetString("currentBuildingAssetBundlePath");
string unityPackagePath = UnityEditor.EditorPrefs.GetString("VRC_exportedUnityPackagePath");
UnityEditor.EditorPrefs.SetBool("VRCSDK2_scene_changed", true);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sex", contentSex.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_violence", contentViolence.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_gore", contentGore.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_other", contentOther.isOn);
if (string.IsNullOrEmpty(apiAvatar.id))
{
pipelineManager.AssignId(PipelineManager.ContentType.avatar);
apiAvatar.id = pipelineManager.blueprintId;
}
string avatarId = apiAvatar.id;
int version = isUpdate ? apiAvatar.version + 1 : 1;
PrepareVRCPathForS3(abPath, avatarId, version, ApiAvatar.VERSION);
if (!string.IsNullOrEmpty(unityPackagePath) && System.IO.File.Exists(unityPackagePath))
{
VRC.Core.Logger.Log("Found unity package path. Preparing to upload!", API.LOG_CATEGORY);
PrepareUnityPackageForS3(unityPackagePath, avatarId, version, ApiAvatar.VERSION);
}
StartCoroutine(UploadNew());
}
IEnumerator UploadNew()
{
bool caughtInvalidInput = false;
if (!ValidateNameInput(blueprintName))
caughtInvalidInput = true;
if (caughtInvalidInput)
yield break;
VRC.Core.Logger.Log("Starting upload");
// upload unity package
if (!string.IsNullOrEmpty(uploadUnityPackagePath))
{
yield return StartCoroutine(UploadFile(uploadUnityPackagePath, "", GetFriendlyAvatarFileName("Unity package"), "Unity package",
delegate (string fileUrl)
{
cloudFrontUnityPackageUrl = fileUrl;
}
));
}
// upload asset bundle
if (!string.IsNullOrEmpty(uploadVrcPath))
{
yield return StartCoroutine(UploadFile(uploadVrcPath, isUpdate ? apiAvatar.assetUrl : "", GetFriendlyAvatarFileName("Asset bundle"), "Asset bundle",
delegate (string fileUrl)
{
cloudFrontAssetUrl = fileUrl;
}
));
}
if (isUpdate)
yield return StartCoroutine(UpdateBlueprint());
else
yield return StartCoroutine(CreateBlueprint());
OnSDKPipelineComplete();
}
private string GetFriendlyAvatarFileName(string type)
{
return "Avatar - " + blueprintName.text + " - " + type + " - " + Application.unityVersion + "_" + ApiWorld.VERSION.ApiVersion +
"_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl();
}
List<string> BuildTags()
{
var tags = new List<string>();
if (contentSex.isOn)
tags.Add("content_sex");
if (contentViolence.isOn)
tags.Add("content_violence");
if (contentGore.isOn)
tags.Add("content_gore");
if (contentOther.isOn)
tags.Add("content_other");
if (APIUser.CurrentUser.hasSuperPowers)
{
if (developerAvatar.isOn)
tags.Add("developer");
}
if (tagFallback.isOn)
tags.Add("author_quest_fallback");
return tags;
}
protected override IEnumerator CreateBlueprint()
{
yield return StartCoroutine(UpdateImage(isUpdate ? apiAvatar.imageUrl : "", GetFriendlyAvatarFileName("Image")));
ApiAvatar avatar = new ApiAvatar
{
id = pipelineManager.blueprintId,
authorName = pipelineManager.user.displayName,
authorId = pipelineManager.user.id,
name = blueprintName.text,
imageUrl = cloudFrontImageUrl,
assetUrl = cloudFrontAssetUrl,
description = blueprintDescription.text,
tags = BuildTags(),
releaseStatus = sharePublic.isOn ? "public" : "private"
};
bool doneUploading = false;
bool wasError = false;
avatar.Post(
(c) =>
{
ApiAvatar savedBP = (ApiAvatar)c.Model;
pipelineManager.blueprintId = savedBP.id;
UnityEditor.EditorPrefs.SetString("blueprintID-" + pipelineManager.GetInstanceID().ToString(), savedBP.id);
doneUploading = true;
},
(c) =>
{
Debug.LogError(c.Error);
SetUploadProgress("Saving Avatar", "Error saving blueprint.", 0.0f);
doneUploading = true;
wasError = true;
});
while (!doneUploading)
yield return null;
if (wasError)
yield return new WaitUntil(() => UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Error saving blueprint.", "Okay"));
}
protected override IEnumerator UpdateBlueprint()
{
bool doneUploading = false;
apiAvatar.name = blueprintName.text;
apiAvatar.description = blueprintDescription.text;
apiAvatar.assetUrl = cloudFrontAssetUrl;
apiAvatar.releaseStatus = sharePublic.isOn ? "public" : "private";
apiAvatar.tags = BuildTags();
if (shouldUpdateImageToggle.isOn)
{
yield return StartCoroutine(UpdateImage(isUpdate ? apiAvatar.imageUrl : "", GetFriendlyAvatarFileName("Image")));
apiAvatar.imageUrl = cloudFrontImageUrl;
}
SetUploadProgress("Saving Avatar", "Almost finished!!", 0.8f);
apiAvatar.Save(
(c) => {doneUploading = true; },
(c) => {
Debug.LogError(c.Error);
SetUploadProgress("Saving Avatar", "Error saving blueprint.", 0.0f);
doneUploading = true;
});
while (!doneUploading)
yield return null;
}
void ToggleUpdateImage(bool isOn)
{
if (isOn)
{
bpImage.enabled = false;
liveBpImage.enabled = true;
}
else
{
bpImage.enabled = true;
liveBpImage.enabled = false;
ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, obj => bpImage.texture = obj, null);
}
}
void OnDestroy()
{
UnityEditor.EditorUtility.ClearProgressBar();
UnityEditor.EditorPrefs.DeleteKey("currentBuildingAssetBundlePath");
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e5ebf65c5dceeb4c909aa7812bd2999
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,669 @@
#define COMMUNITY_LABS_SDK
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using VRC.Core;
using System;
using System.IO;
using VRC.SDK3.Image;
#if UNITY_EDITOR
using UnityEditor;
using VRC.SDKBase;
#endif
namespace VRCSDK2
{
#if UNITY_EDITOR
[Obsolete("Runtime uploads are deprecated. Use methods provided by the VRC.SDKBase.Editor.Api.VRCApi class for uploads")]
public class RuntimeWorldCreation : RuntimeAPICreation
{
public VRC_SceneDescriptor descriptor;
public GameObject waitingPanel;
public GameObject blueprintPanel;
public GameObject errorPanel;
public Text titleText;
public InputField blueprintName;
public InputField blueprintDescription;
public InputField worldCapacity;
public RawImage bpImage;
public Image liveBpImage;
public Toggle shouldUpdateImageToggle;
public Toggle releasePublic;
public Toggle contentNsfw;
public Toggle contentSex;
public Toggle contentViolence;
public Toggle contentGore;
public Toggle contentOther;
public Toggle contentFeatured;
public Toggle contentSDKExample;
public Image showInWorldsMenuGroup;
public Toggle showInActiveWorlds;
public Toggle showInPopularWorlds;
public Toggle showInNewWorlds;
public InputField userTags;
public UnityEngine.UI.Button uploadButton;
public UnityEngine.UI.Button openCommunityLabsDocsButton;
public GameObject publishToCommunityLabsPanel;
private Toggle publishToCommLabsToggle;
private ApiWorld worldRecord;
private const int MAX_USER_TAGS_FOR_WORLD = 5;
private const int MAX_CHARACTERS_ALLOWED_IN_USER_TAG = 20;
List<String> customTags;
public static bool IsCurrentWorldInCommunityLabs = false;
public static bool IsCurrentWorldUploaded = false;
public static bool IsCurrentWorldPubliclyPublished = false;
public static bool HasExceededPublishLimit = false;
new void Start()
{
if (!Application.isEditor || !Application.isPlaying)
return;
base.Start();
IsCurrentWorldInCommunityLabs = false;
IsCurrentWorldUploaded = false;
IsCurrentWorldPubliclyPublished = false;
descriptor = pipelineManager.GetComponent<VRC.SDKBase.VRC_SceneDescriptor>();
descriptor.PositionPortraitCamera(imageCapture.shotCamera.transform);
Application.runInBackground = true;
UnityEngine.XR.XRSettings.enabled = false;
uploadButton.onClick.AddListener(SetupUpload);
openCommunityLabsDocsButton.onClick.AddListener(OpenCommunityLabsDocumentation);
shouldUpdateImageToggle.onValueChanged.AddListener(ToggleUpdateImage);
releasePublic.gameObject.SetActive(false);
System.Action<string> onError = (err) => {
VRC.Core.Logger.LogError("Could not authenticate - " + err);
blueprintPanel.SetActive(false);
errorPanel.SetActive(true);
};
if (!ApiCredentials.Load())
onError("Not logged in");
else
APIUser.InitialFetchCurrentUser(
delegate (ApiModelContainer<APIUser> c)
{
UserLoggedInCallback(c.Model as APIUser);
},
delegate (ApiModelContainer<APIUser> c)
{
onError(c.Error);
}
);
#if !COMMUNITY_LABS_SDK
publishToCommunityLabsPanel.gameObject.SetActive(false);
#endif
}
void UserLoggedInCallback(APIUser user)
{
pipelineManager.user = user;
ApiWorld model = new ApiWorld();
model.id = pipelineManager.blueprintId;
model.Fetch(null,
(c) =>
{
VRC.Core.Logger.Log("<color=magenta>Updating an existing world.</color>", API.LOG_CATEGORY);
worldRecord = c.Model as ApiWorld;
pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(worldRecord.authorId);
GetUserUploadInformationAndSetupUI(model.id);
},
(c) =>
{
VRC.Core.Logger.Log("<color=magenta>World record not found, creating a new world.</color>", API.LOG_CATEGORY);
worldRecord = new ApiWorld { capacity = 16 };
pipelineManager.completedSDKPipeline = false;
worldRecord.id = pipelineManager.blueprintId;
GetUserUploadInformationAndSetupUI(model.id);
});
}
void CheckWorldStatus(string worldId, Action onCheckComplete)
{
// check if world has been previously uploaded, and if world is in community labs
ApiWorld.FetchUploadedWorlds(
delegate (IEnumerable<ApiWorld> worlds)
{
ApiWorld selectedWorld = worlds.FirstOrDefault(w => w.id == worldId);
if (null!=selectedWorld)
{
IsCurrentWorldInCommunityLabs = selectedWorld.IsCommunityLabsWorld;
IsCurrentWorldPubliclyPublished = selectedWorld.IsPublicPublishedWorld;
IsCurrentWorldUploaded = true;
}
if (onCheckComplete != null) onCheckComplete();
},
delegate (string err)
{
IsCurrentWorldInCommunityLabs = false;
IsCurrentWorldUploaded = false;
IsCurrentWorldPubliclyPublished = false;
Debug.Log("CheckWorldStatus error:" + err);
if (onCheckComplete != null) onCheckComplete();
}
);
}
void GetUserUploadInformationAndSetupUI(string worldId)
{
CheckWorldStatus(worldId, delegate()
{
bool hasSufficientTrustLevelToPublishToCommunityLabs = APIUser.CurrentUser.hasKnownTrustLevel;
APIUser.FetchPublishWorldsInformation(
(c) =>
{
try
{
if (c["canPublish"].Type == BestHTTP.JSON.Json.TokenType.Boolean)
{
HasExceededPublishLimit = !(bool)(c["canPublish"]);
}
else
HasExceededPublishLimit = true;
}
catch (Exception)
{
HasExceededPublishLimit = true;
}
if(Application.isPlaying)
{
SetupUI(hasSufficientTrustLevelToPublishToCommunityLabs, HasExceededPublishLimit);
}
},
(c) =>
{
if(Application.isPlaying)
{
SetupUI(hasSufficientTrustLevelToPublishToCommunityLabs, HasExceededPublishLimit);
}
}
);
}
);
}
void SetupUI(bool hasEnoughTrustToPublishToCL = false, bool hasExceededWeeklyPublishLimit = false)
{
#if COMMUNITY_LABS_SDK
// do not display community labs panel if updating an existing CL world or updating a public world
publishToCommunityLabsPanel.gameObject.SetActive(!IsCurrentWorldUploaded);
#endif
if (!ValidateAssetBundleBlueprintID(worldRecord.id))
{
blueprintPanel.SetActive(false);
errorPanel.SetActive(true);
OnSDKPipelineError("The asset bundle is out of date. Please rebuild the scene using 'New Build'.", "The blueprint ID in the scene does not match the id in the asset bundle.");
return;
}
contentFeatured.gameObject.SetActive(APIUser.CurrentUser.hasSuperPowers);
contentSDKExample.gameObject.SetActive(APIUser.CurrentUser.hasSuperPowers);
if (APIUser.Exists(pipelineManager.user))
{
waitingPanel.SetActive(false);
blueprintPanel.SetActive(true);
errorPanel.SetActive(false);
if (string.IsNullOrEmpty(worldRecord.authorId) || worldRecord.authorId == pipelineManager.user.id)
{
titleText.text = "Configure World";
blueprintName.text = worldRecord.name;
worldCapacity.text = worldRecord.capacity.ToString();
contentSex.isOn = worldRecord.tags.Contains("content_sex");
contentViolence.isOn = worldRecord.tags.Contains("content_violence");
contentGore.isOn = worldRecord.tags.Contains("content_gore");
contentOther.isOn = worldRecord.tags.Contains("content_other");
shouldUpdateImageToggle.interactable = isUpdate;
shouldUpdateImageToggle.isOn = !isUpdate;
liveBpImage.enabled = !isUpdate;
bpImage.enabled = isUpdate;
if (!APIUser.CurrentUser.hasSuperPowers)
{
releasePublic.gameObject.SetActive(false);
releasePublic.isOn = false;
releasePublic.interactable = false;
contentFeatured.isOn = contentSDKExample.isOn = false;
}
else
{
contentFeatured.isOn = worldRecord.tags.Contains("content_featured");
contentSDKExample.isOn = worldRecord.tags.Contains("content_sdk_example");
releasePublic.isOn = worldRecord.releaseStatus == "public";
releasePublic.gameObject.SetActive(true);
}
// "show in worlds menu"
if (APIUser.CurrentUser.hasSuperPowers)
{
showInWorldsMenuGroup.gameObject.SetActive(true);
showInActiveWorlds.isOn = !worldRecord.tags.Contains("admin_hide_active");
showInPopularWorlds.isOn = !worldRecord.tags.Contains("admin_hide_popular");
showInNewWorlds.isOn = !worldRecord.tags.Contains("admin_hide_new");
}
else
{
showInWorldsMenuGroup.gameObject.SetActive(false);
}
blueprintDescription.text = worldRecord.description;
userTags.text = "";
foreach (var tag in worldRecord.publicTags)
{
userTags.text = userTags.text + tag.Replace("author_tag_", "");
userTags.text = userTags.text + " ";
}
ImageDownloader.DownloadImage(worldRecord.imageUrl, 0, obj => bpImage.texture = obj, null);
}
else // user does not own world id associated with descriptor
{
Debug.LogErrorFormat("{0} is not an owner of {1}", worldRecord.authorId, pipelineManager.user.id);
blueprintPanel.SetActive(false);
errorPanel.SetActive(true);
}
}
else
{
waitingPanel.SetActive(true);
blueprintPanel.SetActive(false);
errorPanel.SetActive(false);
if (!APIUser.CurrentUser.hasSuperPowers)
{
releasePublic.gameObject.SetActive(false);
releasePublic.isOn = false;
releasePublic.interactable = false;
}
else
{
releasePublic.gameObject.SetActive(true);
releasePublic.isOn = false;
}
}
// set up publish to Community Labs checkbox and text
int worldsPublishedThisWeek = hasExceededWeeklyPublishLimit ? 1 : 0;
int maximumWorldsAllowedToPublishPerWeek = 1;
publishToCommLabsToggle = publishToCommunityLabsPanel.GetComponentInChildren<Toggle>();
if (null != publishToCommLabsToggle)
{
// disable publishing to CL checkbox if not enough trust or exceeded publish limit
publishToCommLabsToggle.interactable = hasEnoughTrustToPublishToCL && !hasExceededWeeklyPublishLimit;
Text publishText = publishToCommLabsToggle.gameObject.GetComponentInChildren<Text>();
if (null != publishText)
{
if (!hasEnoughTrustToPublishToCL)
{
publishText.text = "Not enough Trust to Publish to Community Labs";
}
else
{
if (hasExceededWeeklyPublishLimit)
{
publishText.text = "Publish limit for Community Labs Exceeded\n" + "(" + worldsPublishedThisWeek + "/" + maximumWorldsAllowedToPublishPerWeek + " Published this week)";
}
else
{
publishText.text = "Publish to Community Labs\n" + "(" + worldsPublishedThisWeek + "/" + maximumWorldsAllowedToPublishPerWeek + " Published this week)";
}
}
}
}
}
public void SetupUpload()
{
if (!ParseUserTags())
return;
publishingToCommunityLabs = (publishToCommLabsToggle != null) && (publishToCommLabsToggle.isActiveAndEnabled) && (publishToCommLabsToggle.isOn);
uploadTitle = "Preparing For Upload";
isUploading = true;
string abPath = UnityEditor.EditorPrefs.GetString("currentBuildingAssetBundlePath");
string unityPackagePath = UnityEditor.EditorPrefs.GetString("VRC_exportedUnityPackagePath");
UnityEditor.EditorPrefs.SetBool("VRCSDK2_scene_changed", true);
UnityEditor.EditorPrefs.SetInt("VRCSDK2_capacity", System.Convert.ToInt16(worldCapacity.text));
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sex", contentSex.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_violence", contentViolence.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_gore", contentGore.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_other", contentOther.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_release_public", releasePublic.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_featured", contentFeatured.isOn);
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sdk_example", contentSDKExample.isOn);
if (string.IsNullOrEmpty(worldRecord.id))
{
pipelineManager.AssignId(PipelineManager.ContentType.world);
worldRecord.id = pipelineManager.blueprintId;
}
string blueprintId = worldRecord.id;
int version = Mathf.Max(1, worldRecord.version + 1);
PrepareVRCPathForS3(abPath, blueprintId, version, ApiWorld.VERSION);
if (!string.IsNullOrEmpty(unityPackagePath) && System.IO.File.Exists(unityPackagePath))
{
VRC.Core.Logger.Log("Found unity package path. Preparing to upload!", API.LOG_CATEGORY);
PrepareUnityPackageForS3(unityPackagePath, blueprintId, version, ApiWorld.VERSION);
}
StartCoroutine(UploadNew());
}
void OnUploadedWorld()
{
const string devUrl = "https://dev-api.vrchat.cloud";
const string releaseUrl = "https://vrchat.com";
string uploadedWorldURL = (API.IsDevApi() ? devUrl : releaseUrl) + "/home/world/" + pipelineManager.blueprintId;
OnSDKPipelineComplete(uploadedWorldURL);
}
IEnumerator UploadNew()
{
bool caughtInvalidInput = false;
if (!ValidateNameInput(blueprintName))
caughtInvalidInput = true;
if (caughtInvalidInput)
yield break;
VRC.Core.Logger.Log("Starting upload");
// upload unity package
if (!string.IsNullOrEmpty(uploadUnityPackagePath))
{
yield return StartCoroutine(UploadFile(uploadUnityPackagePath, isUpdate ? worldRecord.unityPackageUrl : "", GetFriendlyWorldFileName("Unity package"), "Unity package",
delegate (string fileUrl)
{
cloudFrontUnityPackageUrl = fileUrl;
}
));
}
// upload asset bundle
if (!string.IsNullOrEmpty(uploadVrcPath))
{
yield return StartCoroutine(UploadFile(uploadVrcPath, isUpdate ? worldRecord.assetUrl : "", GetFriendlyWorldFileName("Asset bundle"), "Asset bundle",
delegate (string fileUrl)
{
cloudFrontAssetUrl = fileUrl;
}
));
}
if (isUpdate)
yield return StartCoroutine(UpdateBlueprint());
else
yield return StartCoroutine(CreateBlueprint());
if (publishingToCommunityLabs)
{
ApiWorld.PublishWorldToCommunityLabs(pipelineManager.blueprintId,
(world) => OnUploadedWorld(),
(err) =>
{
Debug.LogError("PublishWorldToCommunityLabs error:" + err);
OnUploadedWorld();
}
);
}
else
{
OnUploadedWorld();
}
}
private string GetFriendlyWorldFileName(string type)
{
return "World - " + blueprintName.text + " - " + type + " - " + Application.unityVersion + "_" + ApiWorld.VERSION.ApiVersion +
"_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl();
}
List<string> BuildTags()
{
var tags = new List<string>();
if (contentSex.isOn)
tags.Add("content_sex");
if (contentViolence.isOn)
tags.Add("content_violence");
if (contentGore.isOn)
tags.Add("content_gore");
if (contentOther.isOn)
tags.Add("content_other");
if (APIUser.CurrentUser.hasSuperPowers)
{
if (contentFeatured.isOn)
tags.Add("content_featured");
if (contentSDKExample.isOn)
tags.Add("content_sdk_example");
if(releasePublic.isOn)
tags.Add("admin_approved");
}
// "show in worlds menu"
if (APIUser.CurrentUser.hasSuperPowers)
{
if (!showInActiveWorlds.isOn)
tags.Add("admin_hide_active");
if (!showInPopularWorlds.isOn)
tags.Add("admin_hide_popular");
if (!showInNewWorlds.isOn)
tags.Add("admin_hide_new");
}
// add any author tags
foreach (var word in customTags)
{
// add all custom tags with "author_tag_" prefix
tags.Add("author_tag_" + word);
}
return tags;
}
bool ParseUserTags()
{
bool validTags = true;
customTags = new List<string>();
char[] delimiterChars = { ' ', ',', '.', ':', '\t', '\n', '"', '#' };
// split user tags into individual words
string[] words = userTags.text.Split(delimiterChars, StringSplitOptions.RemoveEmptyEntries);
foreach (var word in words)
{
customTags.Add(word.ToLower());
}
// check that number of tags is within tag limit
if (words.Count() > MAX_USER_TAGS_FOR_WORLD)
{
validTags = false;
UnityEditor.EditorUtility.DisplayDialog("Tags are limited to a maximum of " + MAX_USER_TAGS_FOR_WORLD + " per world.", "Please remove excess tags before uploading!", "OK");
}
else
{
// check that no tags exceed maximum tag length
int maximumTagLength = 0;
foreach (string item in words)
{
if (item.Length > maximumTagLength)
{
maximumTagLength = item.Length;
}
}
if (maximumTagLength > MAX_CHARACTERS_ALLOWED_IN_USER_TAG)
{
validTags = false;
UnityEditor.EditorUtility.DisplayDialog("Tags are limited to a maximum of " + MAX_CHARACTERS_ALLOWED_IN_USER_TAG + " characters per tag.", "One or more of your tags exceeds the maximum " + MAX_CHARACTERS_ALLOWED_IN_USER_TAG + " character limit.\n\n" + "Please shorten tags before uploading!", "OK");
}
else
{
// make sure tags are all alphanumeric
foreach (var word in words)
{
if (!word.All(char.IsLetterOrDigit))
{
validTags = false;
UnityEditor.EditorUtility.DisplayDialog("Tags should consist of alphanumeric characters only.", "Please remove any non-alphanumeric characters from tags before uploading!", "OK");
}
}
}
}
return validTags;
}
protected override IEnumerator CreateBlueprint()
{
yield return StartCoroutine(UpdateImage(isUpdate ? worldRecord.imageUrl : "", GetFriendlyWorldFileName("Image")));
SetUploadProgress("Saving Blueprint to user", "Almost finished!!", 0.0f);
ApiWorld world = new ApiWorld
{
id = worldRecord.id,
authorName = pipelineManager.user.displayName,
authorId = pipelineManager.user.id,
name = blueprintName.text,
imageUrl = cloudFrontImageUrl,
assetUrl = cloudFrontAssetUrl,
unityPackageUrl = cloudFrontUnityPackageUrl,
description = blueprintDescription.text,
tags = BuildTags(),
releaseStatus = (releasePublic.isOn) ? ("public") : ("private"),
capacity = System.Convert.ToInt16(worldCapacity.text),
occupants = 0,
shouldAddToAuthor = true,
udonProducts = descriptor.udonProducts
};
if (APIUser.CurrentUser.hasSuperPowers)
world.isCurated = contentFeatured.isOn || contentSDKExample.isOn;
else
world.isCurated = false;
bool doneUploading = false;
world.Post(
(c) =>
{
ApiWorld savedBP = (ApiWorld)c.Model;
pipelineManager.blueprintId = savedBP.id;
UnityEditor.EditorPrefs.SetString("blueprintID-" + pipelineManager.GetInstanceID().ToString(), savedBP.id);
VRC.Core.Logger.Log("Setting blueprintID on pipeline manager and editor prefs", API.LOG_CATEGORY);
doneUploading = true;
},
(c) => { doneUploading = true; Debug.LogError(c.Error); });
while (!doneUploading)
yield return null;
}
protected override IEnumerator UpdateBlueprint()
{
bool doneUploading = false;
worldRecord.name = blueprintName.text;
worldRecord.description = blueprintDescription.text;
worldRecord.capacity = System.Convert.ToInt16(worldCapacity.text);
worldRecord.assetUrl = cloudFrontAssetUrl;
worldRecord.tags = BuildTags();
worldRecord.releaseStatus = (releasePublic.isOn) ? ("public") : ("private");
worldRecord.unityPackageUrl = cloudFrontUnityPackageUrl;
worldRecord.isCurated = contentFeatured.isOn || contentSDKExample.isOn;
worldRecord.udonProducts = descriptor.udonProducts;
if (shouldUpdateImageToggle.isOn)
{
yield return StartCoroutine(UpdateImage(isUpdate ? worldRecord.imageUrl : "", GetFriendlyWorldFileName("Image")));
worldRecord.imageUrl = cloudFrontImageUrl;
}
SetUploadProgress("Saving Blueprint", "Almost finished!!", 0.0f);
worldRecord.Save((c) => doneUploading = true, (c) => { doneUploading = true; Debug.LogError(c.Error); });
while (!doneUploading)
yield return null;
}
void ToggleUpdateImage(bool isOn)
{
if (isOn)
{
bpImage.enabled = false;
liveBpImage.enabled = true;
}
else
{
bpImage.enabled = true;
liveBpImage.enabled = false;
ImageDownloader.DownloadImage(worldRecord.imageUrl, 0, obj => bpImage.texture = obj, null);
}
}
protected override void DisplayUpdateCompletedDialog(string contentUrl = null)
{
}
private void OpenCommunityLabsDocumentation()
{
Application.OpenURL(CommunityLabsConstants.COMMUNITY_LABS_DOCUMENTATION_URL);
}
void OnDestroy()
{
UnityEditor.EditorUtility.ClearProgressBar();
UnityEditor.EditorPrefs.DeleteKey("currentBuildingAssetBundlePath");
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2bd5ee5d69ee0f3449bf2f81fcb7f4e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using System.Collections;
using UnityEngine;
public class SceneSaver
{
static public void SaveScene()
{
#if UNITY_EDITOR
var activeScene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene();
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
UnityEditor.EditorApplication.isPaused = false;
UnityEditor.EditorApplication.isPlaying = false;
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(activeScene.name);
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0d49300ad532d4ae6b569b28de5b7dac
timeCreated: 1446917779
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 48829de75cd107e4a94337ca7cc3dd3c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,132 @@

namespace VRC.SDKBase.Validation
{
public static partial class AvatarValidation
{
public static readonly string[] ComponentTypeWhiteListCommon = new string[]
{
#if UNITY_STANDALONE
#if VRC_CLIENT
"DynamicBone", // Deprecated, whitelisted in the client only for backwards compatibility
"DynamicBoneCollider", // Deprecated, whitelisted in the client only for backwards compatibility
#endif // VRC_CLIENT
"RootMotion.FinalIK.IKExecutionOrder",
"RootMotion.FinalIK.VRIK",
"RootMotion.FinalIK.FullBodyBipedIK",
"RootMotion.FinalIK.LimbIK",
"RootMotion.FinalIK.AimIK",
"RootMotion.FinalIK.BipedIK",
"RootMotion.FinalIK.GrounderIK",
"RootMotion.FinalIK.GrounderFBBIK",
"RootMotion.FinalIK.GrounderVRIK",
"RootMotion.FinalIK.GrounderQuadruped",
"RootMotion.FinalIK.TwistRelaxer",
"RootMotion.FinalIK.ShoulderRotator",
"RootMotion.FinalIK.FBBIKArmBending",
"RootMotion.FinalIK.FBBIKHeadEffector",
"RootMotion.FinalIK.FABRIK",
"RootMotion.FinalIK.FABRIKChain",
"RootMotion.FinalIK.FABRIKRoot",
"RootMotion.FinalIK.CCDIK",
"RootMotion.FinalIK.RotationLimit",
"RootMotion.FinalIK.RotationLimitHinge",
"RootMotion.FinalIK.RotationLimitPolygonal",
"RootMotion.FinalIK.RotationLimitSpline",
"UnityEngine.Cloth",
"UnityEngine.Light",
"UnityEngine.BoxCollider",
"UnityEngine.SphereCollider",
"UnityEngine.CapsuleCollider",
"UnityEngine.Rigidbody",
"UnityEngine.Joint",
"UnityEngine.Animations.AimConstraint",
"UnityEngine.Animations.LookAtConstraint",
"UnityEngine.Animations.ParentConstraint",
"UnityEngine.Animations.PositionConstraint",
"UnityEngine.Animations.RotationConstraint",
"UnityEngine.Animations.ScaleConstraint",
"UnityEngine.Camera",
"UnityEngine.AudioSource",
"ONSPAudioSource",
#endif // UNITY_STANDALONE
#if !VRC_CLIENT
"VRC.Core.PipelineSaver",
#endif
"VRC.Core.PipelineManager",
"UnityEngine.Transform",
"UnityEngine.Animator",
"UnityEngine.SkinnedMeshRenderer",
"LimbIK", // our limbik based on Unity ik
"LoadingAvatarTextureAnimation",
"UnityEngine.MeshFilter",
"UnityEngine.MeshRenderer",
"UnityEngine.Animation",
"UnityEngine.ParticleSystem",
"UnityEngine.ParticleSystemRenderer",
"UnityEngine.TrailRenderer",
"UnityEngine.FlareLayer",
"UnityEngine.GUILayer",
"UnityEngine.LineRenderer",
"RealisticEyeMovements.EyeAndHeadAnimator",
"RealisticEyeMovements.LookTargetController",
};
public static readonly string[] ComponentTypeWhiteListSdk2 = new string[]
{
#if UNITY_STANDALONE
"VRCSDK2.VRC_SpatialAudioSource",
#endif
"VRCSDK2.VRC_AvatarDescriptor",
"VRCSDK2.VRC_AvatarVariations",
"VRCSDK2.VRC_IKFollower",
"VRCSDK2.VRC_Station",
};
public static readonly string[] ComponentTypeWhiteListSdk3 = new string[]
{
#if UNITY_STANDALONE
"VRC.SDK3.Avatars.Components.VRCSpatialAudioSource",
#endif
"VRC.SDK3.VRCTestMarker",
"VRC.SDK3.Avatars.Components.VRCAvatarDescriptor",
"VRC.SDK3.Avatars.Components.VRCStation",
"VRC.SDK3.Avatars.Components.VRCImpostorSettings",
"VRC.SDK3.Avatars.Components.VRCImpostorEnvironment",
"VRC.SDK3.Avatars.Components.VRCHeadChop",
"VRC.SDK3.Avatars.Components.VRCRaycast",
"VRC.SDK3.Dynamics.PhysBone.Components.VRCPhysBone",
"VRC.SDK3.Dynamics.PhysBone.Components.VRCPhysBoneCollider",
"VRC.SDK3.Dynamics.Constraint.Components.VRCAimConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCLookAtConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCParentConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCPositionConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCRotationConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCScaleConstraint",
"VRC.SDK3.Dynamics.Contact.Components.VRCContactSender",
"VRC.SDK3.Dynamics.Contact.Components.VRCContactReceiver",
};
public static readonly string[] ShaderWhiteList = new string[]
{
"VRChat/Mobile/Standard Lite",
"VRChat/Mobile/Diffuse",
"VRChat/Mobile/Bumped Diffuse",
"VRChat/Mobile/Bumped Mapped Specular",
"VRChat/Mobile/Toon Lit",
"VRChat/Mobile/MatCap Lit",
"VRChat/Mobile/Particles/Additive",
"VRChat/Mobile/Particles/Multiply",
"VRChat/Mobile/Toon Standard",
"VRChat/Mobile/Toon Standard (Outline)", // not in client whitelist, will fall back to non-outline variant on mobile
};
public const int MAX_RAYCAST_COMPONENTS_PER_AVATAR = 80; // VRCRaycast + FinalIK
public const int MAX_AVD_PHYSBONES_PER_AVATAR = 256;
public const int MAX_AVD_COLLIDERS_PER_AVATAR = 256;
public const int MAX_AVD_CONTACTS_PER_AVATAR = 256;
public const int MAX_AVD_CONSTRAINTS_PER_AVATAR = 2000;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65a0b1106808685488e7287333ef73e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,805 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
#if TextMeshPro
using TMPro;
#endif
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using UnityEngine.UI;
#if VRC_CLIENT
using ZLinq;
#else
using System.Linq;
#endif
namespace VRC.SDKBase.Validation
{
public static class WorldValidation
{
private static readonly Lazy<string> _debugCategoryName = new Lazy<string>(InitializeLogging);
private static string DebugCategoryName => _debugCategoryName.Value;
private static string InitializeLogging()
{
const string categoryName = "WorldValidation";
VRC.Core.Logger.DescribeCategory(categoryName, VRC.Core.Logger.Color.red);
VRC.Core.Logger.EnableCategory(categoryName);
return categoryName;
}
static string[] ComponentTypeWhiteList = null;
public enum WhiteListConfiguration
{
None,
VRCSDK2,
VRCSDK3,
Unchanged
}
static WhiteListConfiguration ComponentTypeWhiteListConfiguration = WhiteListConfiguration.None;
static readonly string[] ComponentTypeWhiteListCommon = new string[]
{
#if UNITY_STANDALONE
"UnityEngine.Rendering.PostProcessing.PostProcessDebug",
"UnityEngine.Rendering.PostProcessing.PostProcessLayer",
"UnityEngine.Rendering.PostProcessing.PostProcessVolume",
#endif
"VRC.Core.PipelineManager",
"UiInputField",
"VRCProjectSettings",
"DynamicBone",
"DynamicBoneCollider",
"TMPro.TMP_Dropdown",
"TMPro.TMP_InputField",
"TMPro.TMP_ScrollbarEventHandler",
"TMPro.TMP_SelectionCaret",
"TMPro.TMP_SpriteAnimator",
"TMPro.TMP_SubMesh",
"TMPro.TMP_SubMeshUI",
"TMPro.TMP_Text",
"TMPro.TextMeshPro",
"TMPro.TextMeshProUGUI",
"TMPro.TextContainer",
"TMPro.TMP_Dropdown+DropdownItem",
"UnityEngine.EventSystems.EventSystem",
"UnityEngine.EventSystems.EventTrigger",
"UnityEngine.EventSystems.UIBehaviour",
"UnityEngine.EventSystems.BaseInput",
"UnityEngine.EventSystems.BaseInputModule",
"UnityEngine.EventSystems.PointerInputModule",
"UnityEngine.EventSystems.StandaloneInputModule",
"UnityEngine.EventSystems.TouchInputModule",
"UnityEngine.EventSystems.BaseRaycaster",
"UnityEngine.EventSystems.PhysicsRaycaster",
"UnityEngine.UI.Button",
"UnityEngine.UI.Dropdown",
"UnityEngine.UI.Dropdown+DropdownItem",
"UnityEngine.UI.Graphic",
"UnityEngine.UI.GraphicRaycaster",
"UnityEngine.UI.Image",
"UnityEngine.UI.InputField",
"UnityEngine.UI.Mask",
"UnityEngine.UI.MaskableGraphic",
"UnityEngine.UI.RawImage",
"UnityEngine.UI.RectMask2D",
"UnityEngine.UI.Scrollbar",
"UnityEngine.UI.ScrollRect",
"UnityEngine.UI.Selectable",
"UnityEngine.UI.Slider",
"UnityEngine.UI.Text",
"UnityEngine.UI.Toggle",
"UnityEngine.UI.ToggleGroup",
"UnityEngine.UI.AspectRatioFitter",
"UnityEngine.UI.CanvasScaler",
"UnityEngine.UI.ContentSizeFitter",
"UnityEngine.UI.GridLayoutGroup",
"UnityEngine.UI.HorizontalLayoutGroup",
"UnityEngine.UI.HorizontalOrVerticalLayoutGroup",
"UnityEngine.UI.LayoutElement",
"UnityEngine.UI.LayoutGroup",
"UnityEngine.UI.VerticalLayoutGroup",
"UnityEngine.UI.BaseMeshEffect",
"UnityEngine.UI.Outline",
"UnityEngine.UI.PositionAsUV1",
"UnityEngine.UI.Shadow",
"OVRLipSync",
"OVRLipSyncContext",
"OVRLipSyncContextBase",
"OVRLipSyncContextCanned",
"OVRLipSyncContextMorphTarget",
"OVRLipSyncContextTextureFlip",
"ONSPReflectionZone",
"OculusSpatializerUnity",
"ONSPAmbisonicsNative",
"ONSPAudioSource",
"RootMotion.FinalIK.BipedIK",
"RootMotion.FinalIK.FingerRig",
"RootMotion.FinalIK.Grounder",
"RootMotion.FinalIK.GrounderBipedIK",
"RootMotion.FinalIK.GrounderFBBIK",
"RootMotion.FinalIK.GrounderIK",
"RootMotion.FinalIK.GrounderQuadruped",
"RootMotion.FinalIK.GrounderVRIK",
"RootMotion.FinalIK.AimIK",
"RootMotion.FinalIK.CCDIK",
"RootMotion.FinalIK.FABRIK",
"RootMotion.FinalIK.FABRIKRoot",
"RootMotion.FinalIK.FullBodyBipedIK",
"RootMotion.FinalIK.IK",
"RootMotion.FinalIK.IKExecutionOrder",
"RootMotion.FinalIK.LegIK",
"RootMotion.FinalIK.LimbIK",
"RootMotion.FinalIK.LookAtIK",
"RootMotion.FinalIK.TrigonometricIK",
"RootMotion.FinalIK.VRIK",
"RootMotion.FinalIK.FBBIKArmBending",
"RootMotion.FinalIK.FBBIKHeadEffector",
"RootMotion.FinalIK.TwistRelaxer",
"RootMotion.FinalIK.InteractionObject",
"RootMotion.FinalIK.InteractionSystem",
"RootMotion.FinalIK.InteractionTarget",
"RootMotion.FinalIK.InteractionTrigger",
"RootMotion.FinalIK.GenericPoser",
"RootMotion.FinalIK.HandPoser",
"RootMotion.FinalIK.Poser",
"RootMotion.FinalIK.RagdollUtility",
"RootMotion.FinalIK.RotationLimit",
"RootMotion.FinalIK.RotationLimitAngle",
"RootMotion.FinalIK.RotationLimitHinge",
"RootMotion.FinalIK.RotationLimitPolygonal",
"RootMotion.FinalIK.RotationLimitSpline",
"RootMotion.FinalIK.AimPoser",
"RootMotion.FinalIK.Amplifier",
"RootMotion.FinalIK.BodyTilt",
"RootMotion.FinalIK.HitReaction",
"RootMotion.FinalIK.HitReactionVRIK",
"RootMotion.FinalIK.Inertia",
"RootMotion.FinalIK.OffsetModifier",
"RootMotion.FinalIK.OffsetModifierVRIK",
"RootMotion.FinalIK.OffsetPose",
"RootMotion.FinalIK.Recoil",
"RootMotion.FinalIK.ShoulderRotator",
"RootMotion.Dynamics.AnimationBlocker",
"RootMotion.Dynamics.BehaviourBase",
"RootMotion.Dynamics.BehaviourFall",
"RootMotion.Dynamics.BehaviourPuppet",
"RootMotion.Dynamics.JointBreakBroadcaster",
"RootMotion.Dynamics.MuscleCollisionBroadcaster",
"RootMotion.Dynamics.PressureSensor",
"RootMotion.Dynamics.Prop",
"RootMotion.Dynamics.PropRoot",
"RootMotion.Dynamics.PuppetMaster",
"RootMotion.Dynamics.PuppetMasterSettings",
// TODO: remove these if they are only needed in editor
"RootMotion.Dynamics.BipedRagdollCreator",
"RootMotion.Dynamics.RagdollCreator",
"RootMotion.Dynamics.RagdollEditor",
//
"RootMotion.SolverManager",
"RootMotion.TriggerEventBroadcaster",
"UnityEngine.WindZone",
"UnityEngine.Tilemaps.Tilemap",
"UnityEngine.Tilemaps.TilemapRenderer",
"UnityEngine.Terrain",
"UnityEngine.Tree",
"UnityEngine.SpriteMask",
"UnityEngine.Grid",
"UnityEngine.GridLayout",
"UnityEngine.AudioSource",
"UnityEngine.AudioReverbZone",
"UnityEngine.AudioLowPassFilter",
"UnityEngine.AudioHighPassFilter",
"UnityEngine.AudioDistortionFilter",
"UnityEngine.AudioEchoFilter",
"UnityEngine.AudioChorusFilter",
"UnityEngine.AudioReverbFilter",
"UnityEngine.AudioListener", // will be force-disabled on load, but can't be removed since it's a dependency for some of the above
"UnityEngine.Playables.PlayableDirector",
"UnityEngine.TerrainCollider",
"UnityEngine.Canvas",
"UnityEngine.CanvasGroup",
"UnityEngine.CanvasRenderer",
"UnityEngine.TextMesh",
"UnityEngine.Animator",
"UnityEngine.AI.NavMeshAgent",
"UnityEngine.AI.NavMeshObstacle",
#if UNITY_6000_0_OR_NEWER
"Unity.AI.Navigation.NavMeshLink",
#else
"UnityEngine.AI.OffMeshLink",
#endif
"UnityEngine.Cloth",
"UnityEngine.WheelCollider",
"UnityEngine.Rigidbody",
"UnityEngine.Joint",
"UnityEngine.HingeJoint",
"UnityEngine.SpringJoint",
"UnityEngine.FixedJoint",
"UnityEngine.CharacterJoint",
"UnityEngine.ConfigurableJoint",
"UnityEngine.ConstantForce",
"UnityEngine.Collider",
"UnityEngine.BoxCollider",
"UnityEngine.SphereCollider",
"UnityEngine.MeshCollider",
"UnityEngine.CapsuleCollider",
"UnityEngine.CharacterController",
"UnityEngine.ParticleSystem",
"UnityEngine.ParticleSystemRenderer",
"UnityEngine.BillboardRenderer",
"UnityEngine.Camera",
"UnityEngine.FlareLayer",
"UnityEngine.SkinnedMeshRenderer",
"UnityEngine.Renderer",
"UnityEngine.TrailRenderer",
"UnityEngine.LineRenderer",
"UnityEngine.GUIElement",
"UnityEngine.GUILayer",
"UnityEngine.Light",
"UnityEngine.LightProbeGroup",
"UnityEngine.LightProbeProxyVolume",
"UnityEngine.LODGroup",
"UnityEngine.ReflectionProbe",
"UnityEngine.SpriteRenderer",
"UnityEngine.Transform",
"UnityEngine.RectTransform",
"UnityEngine.Rendering.SortingGroup",
"UnityEngine.Projector",
"UnityEngine.OcclusionPortal",
"UnityEngine.OcclusionArea",
"UnityEngine.LensFlare",
"UnityEngine.Skybox",
"UnityEngine.MeshFilter",
"UnityEngine.Halo",
"UnityEngine.MeshRenderer",
"UnityEngine.Collider2D",
"UnityEngine.Rigidbody2D",
"UnityEngine.CompositeCollider2D",
"UnityEngine.ConstantForce2D",
"UnityEngine.AreaEffector2D",
"UnityEngine.CapsuleCollider2D",
"UnityEngine.DistanceJoint2D",
"UnityEngine.EdgeCollider2D",
"UnityEngine.Effector2D",
"UnityEngine.BoxCollider2D",
"UnityEngine.CircleCollider2D",
"UnityEngine.FixedJoint2D",
"UnityEngine.HingeJoint2D",
"UnityEngine.FrictionJoint2D",
"UnityEngine.PlatformEffector2D",
"UnityEngine.PointEffector2D",
"UnityEngine.PolygonCollider2D",
"UnityEngine.SliderJoint2D",
"UnityEngine.SurfaceEffector2D",
"UnityEngine.RelativeJoint2D",
"UnityEngine.TargetJoint2D",
"UnityEngine.WheelJoint2D",
"UnityEngine.Joint2D",
"UnityEngine.ParticleSystemForceField"
};
static readonly string[] ComponentTypeWhiteListSdk2 = new string[]
{
#if UNITY_STANDALONE
"VRCSDK2.VRC_CustomRendererBehaviour",
"VRCSDK2.VRC_MidiNoteIn",
"VRCSDK2.scripts.Scenes.VRC_Panorama",
"VRCSDK2.VRC_Water",
"UnityStandardAssets.Water.WaterBasic",
"UnityStandardAssets.Water.Displace",
"UnityStandardAssets.Water.GerstnerDisplace",
"UnityStandardAssets.Water.PlanarReflection",
"UnityStandardAssets.Water.SpecularLighting",
"UnityStandardAssets.Water.Water",
"UnityStandardAssets.Water.WaterBase",
"UnityStandardAssets.Water.WaterTile",
#endif
"VRCSDK2.VRCTriggerRelay",
"VRCSDK2.VRC_AudioBank",
"VRCSDK2.VRC_DataStorage",
"VRCSDK2.VRC_EventHandler",
"VRCSDK2.VRC_IKFollower",
"VRCSDK2.VRC_Label",
"VRCSDK2.VRC_KeyEvents",
"VRCSDK2.VRC_PhysicsRoot",
"VRCSDK2.VRC_CombatSystem",
"VRCSDK2.VRC_DestructibleStandard",
"VRC_VisualDamage",
"VRCSDK2.VRC_OscButtonIn",
"VRCSDK2.VRC_GunStats",
"VRCSDK2.VRC_JukeBox",
"VRCSDK2.VRC_AddDamage",
"VRCSDK2.VRC_AddHealth",
"VRCSDK2.VRC_AvatarCalibrator",
"VRCSDK2.VRC_AvatarPedestal",
"VRCSDK2.VRC_NPCSpawn",
"VRCSDK2.VRC_ObjectSpawn",
"VRCSDK2.VRC_ObjectSync",
"VRCSDK2.VRC_Pickup",
"VRCSDK2.VRC_PortalMarker",
"VRCSDK2.VRC_SlideShow",
"VRCSDK2.VRC_SpatialAudioSource",
"VRCSDK2.VRC_StationInput",
"VRCSDK2.VRC_SyncAnimation",
"VRCSDK2.VRC_SyncVideoPlayer",
"VRCSDK2.VRC_SyncVideoStream",
"VRCSDK2.VRC_VideoScreen",
"VRCSDK2.VRC_VideoSpeaker",
"VRCSDK2.VRC_PlayerAudioOverride",
"VRCSDK2.VRC_MirrorReflection",
"VRCSDK2.VRC_PlayerMods",
"VRCSDK2.VRC_SceneDescriptor",
"VRCSDK2.VRC_SceneResetPosition",
"VRCSDK2.VRC_SceneSmoothShift",
"VRCSDK2.VRC_SpecialLayer",
"VRCSDK2.VRC_Station",
"VRCSDK2.VRC_StereoObject",
"VRCSDK2.VRC_TimedEvents",
"VRCSDK2.VRC_Trigger",
"VRCSDK2.VRC_TriggerColliderEventTrigger",
"VRCSDK2.VRC_UseEvents",
"VRCSDK2.VRC_UiShape",
"UnityEngine.Animation",
#if !UNITY_2019_4_OR_NEWER
"UnityEngine.GUIText",
"UnityEngine.GUITexture",
#endif
"UnityEngine.Video.VideoPlayer",
"PhysSound.PhysSoundBase",
"PhysSound.PhysSoundObject",
"PhysSound.PhysSoundTempAudio",
"PhysSound.PhysSoundTempAudioPool",
"PhysSound.PhysSoundTerrain",
"RealisticEyeMovements.EyeAndHeadAnimator",
"RealisticEyeMovements.LookTargetController",
"UnityStandardAssets.Cameras.AbstractTargetFollower",
"UnityStandardAssets.Cameras.AutoCam",
"UnityStandardAssets.Cameras.FreeLookCam",
"UnityStandardAssets.Cameras.HandHeldCam",
"UnityStandardAssets.Cameras.LookatTarget",
"UnityStandardAssets.Cameras.PivotBasedCameraRig",
"UnityStandardAssets.Cameras.ProtectCameraFromWallClip",
"UnityStandardAssets.Cameras.TargetFieldOfView",
"UnityStandardAssets.Characters.FirstPerson.FirstPersonController",
"UnityStandardAssets.Characters.FirstPerson.HeadBob",
"UnityStandardAssets.Characters.FirstPerson.RigidbodyFirstPersonController",
"UnityStandardAssets.Vehicles.Ball.Ball",
"UnityStandardAssets.Vehicles.Ball.BallUserControl",
"UnityStandardAssets.Characters.ThirdPerson.AICharacterControl",
"UnityStandardAssets.Characters.ThirdPerson.ThirdPersonCharacter",
"UnityStandardAssets.Characters.ThirdPerson.ThirdPersonUserControl",
"UnityStandardAssets.CrossPlatformInput.AxisTouchButton",
"UnityStandardAssets.CrossPlatformInput.ButtonHandler",
"UnityStandardAssets.CrossPlatformInput.InputAxisScrollbar",
"UnityStandardAssets.CrossPlatformInput.Joystick",
"UnityStandardAssets.CrossPlatformInput.MobileControlRig",
"UnityStandardAssets.CrossPlatformInput.TiltInput",
"UnityStandardAssets.CrossPlatformInput.TouchPad",
"UnityStandardAssets.Effects.AfterburnerPhysicsForce",
"UnityStandardAssets.Effects.ExplosionFireAndDebris",
"UnityStandardAssets.Effects.ExplosionPhysicsForce",
"UnityStandardAssets.Effects.Explosive",
"UnityStandardAssets.Effects.ExtinguishableParticleSystem",
"UnityStandardAssets.Effects.FireLight",
"UnityStandardAssets.Effects.Hose",
"UnityStandardAssets.Effects.ParticleSystemMultiplier",
"UnityStandardAssets.Effects.SmokeParticles",
"UnityStandardAssets.Effects.WaterHoseParticles",
"UnityStandardAssets.Utility.ActivateTrigger",
"UnityStandardAssets.Utility.AutoMoveAndRotate",
"UnityStandardAssets.Utility.DragRigidbody",
"UnityStandardAssets.Utility.DynamicShadowSettings",
"UnityStandardAssets.Utility.FollowTarget",
"UnityStandardAssets.Utility.FPSCounter",
"UnityStandardAssets.Utility.ObjectResetter",
"UnityStandardAssets.Utility.ParticleSystemDestroyer",
#if !UNITY_2019_4_OR_NEWER
"UnityStandardAssets.Utility.SimpleActivatorMenu",
#endif
"UnityStandardAssets.Utility.SimpleMouseRotator",
"UnityStandardAssets.Utility.SmoothFollow",
"UnityStandardAssets.Utility.TimedObjectActivator",
"UnityStandardAssets.Utility.TimedObjectDestructor",
"UnityStandardAssets.Utility.WaypointCircuit",
"UnityStandardAssets.Utility.WaypointProgressTracker",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneAiControl",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneAudio",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneController",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneControlSurfaceAnimator",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplanePropellerAnimator",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneUserControl2Axis",
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneUserControl4Axis",
"UnityStandardAssets.Vehicles.Aeroplane.JetParticleEffect",
"UnityStandardAssets.Vehicles.Aeroplane.LandingGear",
"UnityStandardAssets.Vehicles.Car.BrakeLight",
"UnityStandardAssets.Vehicles.Car.CarAIControl",
"UnityStandardAssets.Vehicles.Car.CarAudio",
"UnityStandardAssets.Vehicles.Car.CarController",
"UnityStandardAssets.Vehicles.Car.CarSelfRighting",
"UnityStandardAssets.Vehicles.Car.CarUserControl",
"UnityStandardAssets.Vehicles.Car.Mudguard",
"UnityStandardAssets.Vehicles.Car.SkidTrail",
"UnityStandardAssets.Vehicles.Car.Suspension",
"UnityStandardAssets.Vehicles.Car.WheelEffects",
"RenderHeads.Media.AVProVideo.ApplyToMaterial",
"RenderHeads.Media.AVProVideo.ApplyToMesh",
"RenderHeads.Media.AVProVideo.AudioOutput",
"RenderHeads.Media.AVProVideo.DisplayIMGUI",
"RenderHeads.Media.AVProVideo.DisplayUGUI",
"RenderHeads.Media.AVProVideo.MediaPlayer",
"RenderHeads.Media.AVProVideo.SubtitlesUGUI",
"AlphaButtonClickMask",
"EventSystemChecker"
};
static readonly string[] ComponentTypeWhiteListSdk3 = new string[]
{
"VRC.SDK3.VRCDestructibleStandard",
"VRC.SDK3.Components.VRCVisualDamage",
"VRC.SDK3.Components.VRCAvatarPedestal",
"VRC.SDK3.Components.VRCPickup",
"VRC.SDK3.Components.VRCPortalMarker",
"VRC.SDK3.Components.VRCSpatialAudioSource",
"VRC.SDK3.Components.VRCMirrorReflection",
"VRC.SDK3.Components.VRCSceneDescriptor",
"VRC.SDK3.Components.VRCStation",
"VRC.SDK3.Components.VRCUiShape",
"VRC.SDK3.Components.VRCObjectSync",
"VRC.SDK3.Components.VRCObjectPool",
"VRC.SDK3.Components.VRCInputFieldKeyboardOverride",
#if VRC_ENABLE_PLAYER_PERSISTENCE
"VRC.SDK3.Components.VRCPlayerObject",
#endif
#if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE
"VRC.SDK3.Components.VRCEnablePersistence",
#endif
"VRC.SDK3.Video.Components.VRCUnityVideoPlayer",
"VRC.SDK3.Video.Components.AVPro.VRCAVProVideoPlayer",
"VRC.SDK3.Video.Components.AVPro.VRCAVProVideoScreen",
"VRC.SDK3.Video.Components.AVPro.VRCAVProVideoSpeaker",
"VRC.SDK3.Midi.VRCMidiListener",
"VRC.SDK3.Midi.VRCMidiPlayer",
"VRC.SDK3.Components.VRCCameraDollyAnimation",
"VRC.SDK3.Components.VRCCameraDollyPath",
"VRC.SDK3.Components.VRCCameraDollyPathPoint",
"VRC.Udon.UdonBehaviour",
"VRC.Udon.AbstractUdonBehaviourEventProxy",
"VRC.SDK3.Dynamics.PhysBone.Components.VRCPhysBone",
"VRC.SDK3.Dynamics.PhysBone.Components.VRCPhysBoneCollider",
"VRC.SDK3.Dynamics.PhysBone.Components.VRCPhysBoneRoot",
"VRC.SDK3.Dynamics.Constraint.Components.VRCAimConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCLookAtConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCParentConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCPositionConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCRotationConstraint",
"VRC.SDK3.Dynamics.Constraint.Components.VRCScaleConstraint",
"VRC.SDK3.Dynamics.Contact.Components.VRCContactSender",
"VRC.SDK3.Dynamics.Contact.Components.VRCContactReceiver",
"UnityEngine.Animations.AimConstraint",
"UnityEngine.Animations.LookAtConstraint",
"UnityEngine.Animations.ParentConstraint",
"UnityEngine.Animations.PositionConstraint",
"UnityEngine.Animations.RotationConstraint",
"UnityEngine.Animations.ScaleConstraint",
"UnityEngine.ParticleSystemForceField",
"Unity.AI.Navigation.NavMeshSurface",
"Unity.AI.Navigation.NavMeshLink",
"Unity.AI.Navigation.NavMeshModifier",
"Unity.AI.Navigation.NavMeshModifierVolume",
"Cinemachine.Cinemachine3rdPersonAim",
"Cinemachine.CinemachineBlendListCamera",
"Cinemachine.CinemachineBrain",
"Cinemachine.CinemachineCameraOffset",
"Cinemachine.CinemachineClearShot",
"Cinemachine.CinemachineCollider",
"Cinemachine.CinemachineConfiner",
"Cinemachine.CinemachineDollyCart",
"Cinemachine.CinemachineExternalCamera",
"Cinemachine.CinemachineFollowZoom",
"Cinemachine.CinemachineFreeLook",
"Cinemachine.CinemachineMixingCamera",
"Cinemachine.CinemachinePath",
"Cinemachine.CinemachinePipeline",
"Cinemachine.CinemachinePixelPerfect",
"Cinemachine.CinemachineRecomposer",
"Cinemachine.CinemachineSmoothPath",
"Cinemachine.CinemachineStateDrivenCamera",
"Cinemachine.CinemachineStoryboard",
"Cinemachine.CinemachineTargetGroup",
"Cinemachine.CinemachineVirtualCamera",
"Cinemachine.Cinemachine3rdPersonFollow",
"Cinemachine.CinemachineBasicMultiChannelPerlin",
"Cinemachine.CinemachineComposer",
"Cinemachine.CinemachineFramingTransposer",
"Cinemachine.CinemachineGroupComposer",
"Cinemachine.CinemachineHardLockToTarget",
"Cinemachine.CinemachineHardLookAt",
"Cinemachine.CinemachineOrbitalTransposer",
"Cinemachine.CinemachinePOV",
"Cinemachine.CinemachineSameAsFollowTarget",
"Cinemachine.CinemachineTrackedDolly",
"Cinemachine.CinemachineTransposer",
"Cinemachine.CinemachineCore",
};
public static readonly string[] ShaderWhiteList = new string[]
{
"VRChat/Mobile/Standard Lite",
"VRChat/Mobile/Diffuse",
"VRChat/Mobile/Bumped Diffuse",
"VRChat/Mobile/Bumped Mapped Specular",
"VRChat/Mobile/Toon Lit",
"VRChat/Mobile/MatCap Lit",
"VRChat/Mobile/Lightmapped",
"VRChat/Mobile/Skybox",
"VRChat/Mobile/Particles/Additive",
"VRChat/Mobile/Particles/Multiply",
"VRChat/Mobile/World/Supersampled UI",
"FX/MirrorReflection",
"UI/Default",
};
private static readonly HashSet<int> scannedObjects = new HashSet<int>();
private static void ConfigureWhiteList(WhiteListConfiguration config)
{
if(ComponentTypeWhiteListConfiguration == config ||
config == WhiteListConfiguration.Unchanged)
{
return;
}
List<string> concatenation = new List<string>();
concatenation.AddRange(ComponentTypeWhiteListCommon);
switch(config)
{
case WhiteListConfiguration.VRCSDK2:
concatenation.AddRange(ComponentTypeWhiteListSdk2);
break;
case WhiteListConfiguration.VRCSDK3:
concatenation.AddRange(ComponentTypeWhiteListSdk3);
break;
}
ComponentTypeWhiteListConfiguration = config;
ComponentTypeWhiteList = concatenation.ToArray();
}
[PublicAPI]
public static void RemoveIllegalComponents(List<GameObject> targets, WhiteListConfiguration config, bool retry = true, HashSet<Type> tagWhitelistedTypes = null)
{
ConfigureWhiteList(config);
HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes($"world{config}", ComponentTypeWhiteList);
// combine whitelist types from world tags with cached whitelist
tagWhitelistedTypes?.UnionWith(whitelist);
foreach(GameObject target in targets)
{
#if VRC_CLIENT
ValidationUtils.RemoveIllegalComponents(target, tagWhitelistedTypes ?? whitelist, retry, true);
#else
ValidationUtils.RemoveIllegalComponents(target, tagWhitelistedTypes ?? whitelist, retry, true, excludeEditorOnly:true, allowRemovingAssets:false);
#endif
SecurityScan(target);
AddScanned(target);
// Must be called after AddScanned to avoid infinite recursion.
ScanDropdownTemplates(target, whitelist, config == WhiteListConfiguration.VRCSDK3);
}
}
private static void AddScanned(GameObject obj)
{
if(obj == null)
return;
if(!scannedObjects.Contains(obj.GetInstanceID()))
scannedObjects.Add(obj.GetInstanceID());
for(int idx = 0; idx < obj.transform.childCount; ++idx)
AddScanned(obj.transform.GetChild(idx)?.gameObject);
}
private static bool WasScanned(GameObject obj)
{
return scannedObjects.Contains(obj.GetInstanceID());
}
[PublicAPI]
public static void ScanGameObject(GameObject target, WhiteListConfiguration config)
{
if(WasScanned(target))
{
return;
}
ConfigureWhiteList(config);
HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes("world" + config, ComponentTypeWhiteList);
ScanGameObject(target, whitelist, config == WhiteListConfiguration.VRCSDK3);
}
private static void ScanGameObject(GameObject target, HashSet<Type> whitelist, bool isSDK3)
{
if(WasScanned(target))
{
return;
}
#if VRC_CLIENT
ValidationUtils.RemoveIllegalComponents(target, whitelist);
#else
ValidationUtils.RemoveIllegalComponents(target, whitelist, excludeEditorOnly:true, allowRemovingAssets:false);
#endif
SecurityScan(target);
#if VRC_CLIENT && UDON
if (isSDK3)
{
Core.UnityEventFilter.FilterEvents(target);
}
#endif
AddScanned(target);
// Must be called after AddScanned to avoid infinite recursion.
ScanDropdownTemplates(target, whitelist, isSDK3);
}
[PublicAPI]
public static void ClearScannedGameObjectCache()
{
scannedObjects.Clear();
}
[PublicAPI]
public static IEnumerable<Shader> FindIllegalShaders(GameObject target)
{
return ValidationUtils.FindIllegalShaders(target, ShaderWhiteList);
}
private static void SecurityScan(GameObject target)
{
PlayableDirector[] playableDirectors = target.GetComponentsInChildren<PlayableDirector>(true);
foreach(PlayableDirector playableDirector in playableDirectors)
{
StripPlayableDirectorWithPrefabs(playableDirector);
}
UnityEngine.Video.VideoPlayer[] videoPlayers = target.GetComponentsInChildren<UnityEngine.Video.VideoPlayer>(true);
foreach (UnityEngine.Video.VideoPlayer videoPlayer in videoPlayers)
{
AddAudioSourceToVideoPlayer(videoPlayer);
}
}
private static void ScanDropdownTemplates(GameObject target, HashSet<Type> whitelist, bool isSDK3)
{
Dropdown[] dropdowns = target.GetComponentsInChildren<Dropdown>(true);
foreach(Dropdown dropdown in dropdowns)
{
if(dropdown == null)
{
continue;
}
RectTransform dropdownTemplate = dropdown.template;
if(dropdownTemplate == null)
{
continue;
}
ScanGameObject(dropdownTemplate.transform.root.gameObject, whitelist, isSDK3);
}
#if TextMeshPro
TMP_Dropdown[] tmpDropdowns = target.GetComponentsInChildren<TMP_Dropdown>(true);
foreach(TMP_Dropdown textMeshProDropdown in tmpDropdowns)
{
if(textMeshProDropdown == null)
{
continue;
}
RectTransform dropdownTemplate = textMeshProDropdown.template;
if(dropdownTemplate == null)
{
continue;
}
ScanGameObject(dropdownTemplate.transform.root.gameObject, whitelist, isSDK3);
}
#endif
}
private static void StripPlayableDirectorWithPrefabs(PlayableDirector playableDirector)
{
if(!(playableDirector.playableAsset is UnityEngine.Timeline.TimelineAsset timelineAsset))
return;
var tracks = timelineAsset.GetOutputTracks().Concat(timelineAsset.GetRootTracks());
foreach(TrackAsset track in tracks)
{
if(!(track is ControlTrack))
continue;
IEnumerable<TimelineClip> clips = track.GetClips();
foreach(TimelineClip clip in clips)
{
if(clip.asset is ControlPlayableAsset controlPlayableAsset && controlPlayableAsset.prefabGameObject != null)
{
UnityEngine.Object.Destroy(playableDirector);
VRC.Core.Logger.LogWarning("PlayableDirector containing prefab removed", DebugCategoryName, playableDirector.gameObject);
return;
}
}
}
if (!playableDirector.playableGraph.IsValid())
return;
var audioOutputCount = playableDirector.playableGraph.GetOutputCountByType<AudioPlayableOutput>();
for (int i = 0; i < audioOutputCount; i++)
{
var output = (AudioPlayableOutput)playableDirector.playableGraph.GetOutputByType<AudioPlayableOutput>(i);
if (!output.IsOutputValid()) continue;
if (output.GetTarget() == null)
{
// AudioPlayableOutput without a target source will bypass client volume controls.
VRC.Core.Logger.LogWarning("Fixing up AudioPlayableOutput without a target source.", DebugCategoryName, playableDirector.gameObject);
var addedObj = new GameObject("AudioSource_For_" + playableDirector.name);
var addedSrc = addedObj.AddComponent<AudioSource>();
addedSrc.spatialize = false;
addedSrc.bypassEffects = true;
addedSrc.bypassListenerEffects = true;
addedSrc.bypassReverbZones = true;
addedSrc.spatialBlend = 0.0f;
addedSrc.dopplerLevel = 0.0f;
addedSrc.rolloffMode = AudioRolloffMode.Custom;
addedSrc.SetCustomCurve(AudioSourceCurveType.CustomRolloff, AnimationCurve.Constant(0, 1, 1));
#if VRC_CLIENT
addedSrc.outputAudioMixerGroup = VRCAudioManager.GetGameGroup();
#endif
output.SetTarget(addedSrc);
}
}
}
private static void AddAudioSourceToVideoPlayer(UnityEngine.Video.VideoPlayer videoPlayer)
{
// VideoPlayer objects with output mode set to "Direct" bypass client volume controls.
if (videoPlayer.audioOutputMode == UnityEngine.Video.VideoAudioOutputMode.Direct)
{
//if playback is happening (or will) you have to Stop() before you attach an AudioSource.
bool play_state = (videoPlayer.isPlaying || videoPlayer.playOnAwake);
if (play_state)
{
videoPlayer.Stop();
}
AudioSource vp_src = videoPlayer.gameObject.AddComponent<AudioSource>();
videoPlayer.audioOutputMode = UnityEngine.Video.VideoAudioOutputMode.AudioSource;
for (ushort i = 0; i < videoPlayer.audioTrackCount; i++)
{
videoPlayer.SetTargetAudioSource(i,vp_src);
}
if (play_state)
{
videoPlayer.Play();
}
VRC.Core.Logger.LogWarning("VideoPlayer using DIRECT audio output fixed.");
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9b03724cd556cb047b2da80492ea28a5
timeCreated: 1504829091
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: