From a3755591e7fd40577baafcf4fc14107ec355b5fe Mon Sep 17 00:00:00 2001 From: splootybean Date: Thu, 2 Jul 2026 08:22:39 +0000 Subject: [PATCH] Upload files to "/" --- Core.cs | 787 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 787 insertions(+) create mode 100644 Core.cs diff --git a/Core.cs b/Core.cs new file mode 100644 index 0000000..98ef919 --- /dev/null +++ b/Core.cs @@ -0,0 +1,787 @@ +using AmplitudeMiniJSON; +using BestHTTP; +using BestHTTP.Forms; +using BestHTTP.Logger; +using Discord; +using ExitGames.Client.Photon.LoadBalancing; +using RecRoom.Async; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using UnityEditor.Build.Content; +using UnityEditor.VersionControl; +using UnityEngine; +using UnityEngine.Diagnostics; +using static UnityEngine.Networking.SyncList; + +namespace RecNet +{ + public class Core + { + public delegate void ApiCallback(string error); + + public delegate void ApiCallback(string error, T result); + + [Serializable] + private class NameServerResponse + { + // This too :P + public string RecNetStatus; + + public string Auth; + + public string API; + + public string WWW; + + public string Notifications; + + public string Images; + + // new adds... + public string CDN; + + public string Commerce; + + public string Matchmaking; + + public string Storage; + + public string Chat; + + public string Leaderboard; + + public string Accounts; + + public string Rooms; + } + + private class VersionCheckResponse : IRecNetObject + { + public bool ValidVersion { get; private set; } + + public void Deserialize(Dictionary dict) + { + ValidVersion = Util.GetKey("ValidVersion", dict); + } + } + + private delegate HTTPRequest ConstructRequestCallback(Uri uri); + + private delegate IEnumerator AsyncConstructRequestCallback(Uri uri, Action callback); + + private class QueuedApiCall + { + public Service Service; + public string Uri; + public ConstructRequestCallback ConstructRequest; + public AsyncConstructRequestCallback AsyncConstructRequest; + public Promise Promise; + + public QueuedApiCall(Service service, string uri, ConstructRequestCallback constructRequest) + { + Service = service; + Uri = uri; + ConstructRequest = constructRequest; + AsyncConstructRequest = null; + Promise = new Promise(); + } + + public QueuedApiCall(Service service, string uri, AsyncConstructRequestCallback asyncConstructRequest) + { + Service = service; + Uri = uri; + ConstructRequest = null; + AsyncConstructRequest = asyncConstructRequest; + Promise = new Promise(); + } + } + + private const int API_RETRIES = 3; + + private const int PARALLEL_WORKER_THREAD_COUNT = 4; + + private const int MAX_BACKOFF_EXPONENT = 6; + + private const float REFRESH_LOGIN_INTERVAL = 3600f; + + public static readonly Uri RecNetOfflineUri; + + public const bool REC_NET_OFFLINE_MODE = false; + + public static readonly string RecNetEnv; + + private static bool inScreenMode; + + private static bool initialized; + + private static Queue loginQueue; + + private static Queue serialQueue; + + private static Queue parallelQueue; + + private static Queue imageQueue; + + private static Queue eventQueue; + + public static long LocalProfileId { get; private set; } + + private static Dictionary ServiceUris; + + private static HashSet HostsRequiringRecNetAccessToken; + + public static bool IsProdOrStagingEnv => string.IsNullOrWhiteSpace(RecNetEnv) || RecNetEnv.Equals("Prod", StringComparison.OrdinalIgnoreCase) || RecNetEnv.Equals("Staging", StringComparison.OrdinalIgnoreCase); + + public static bool IsDevBuildOrDevEnv => !IsProdOrStagingEnv; + + public static bool InScreenMode + { + get + { + return inScreenMode; + } + set + { + if (inScreenMode != value) + { + inScreenMode = value; + } + } + } + + static Core() + { + RecNetOfflineUri = new Uri("http://offline"); + inScreenMode = true; + initialized = false; + + ServiceUris = new Dictionary(); + HostsRequiringRecNetAccessToken = new HashSet(); + + loginQueue = new Queue(); + serialQueue = new Queue(); + parallelQueue = new Queue(); + imageQueue = new Queue(); + eventQueue = new Queue(); + + HTTPManager.UseAlternateSSLDefaultValue = true; +#if !UNITY_EDITOR + HTTPManager.Logger.Level = Loglevels.All; +#endif + + string[] commandLineArgs = Environment.GetCommandLineArgs(); + if (commandLineArgs == null) + { + return; + } + string[] array = commandLineArgs; + foreach (string text in array) + { + if (text != null && text.StartsWith("+RecNet:", StringComparison.InvariantCultureIgnoreCase)) + { + RecNetEnv = text.Substring("+RecNet:".Length); + } + } + } + + public static IPromise Get(Service service, string requestUri, bool executeSerially = true) + { + return CallApi(service, requestUri, (Uri uri) => new HTTPRequest(uri, HTTPMethods.Get), executeSerially); + } + + public static IPromise Get(string requestUri, bool executeSerially = true) + { + return Get(Service.API, requestUri, executeSerially); + } + + public static IPromise GetFromCDN(string requestUri) + { + return Get(Service.CDN, requestUri, false); + } + + public static IPromise GetFromImageCDN(string requestUri) + { + return CallApi(Service.Images, requestUri, (Uri uri) => new HTTPRequest(uri, HTTPMethods.Get), imageQueue); + } + + public static IPromise Post(Service service, string requestUri, Dictionary form, bool executeSerially = true) + { + return CallApi(service, requestUri, (Uri uri) => ConstructSimpleFormPost(uri, form), executeSerially); + } + + public static IPromise Post(Service service, string requestUri, WWWForm form, bool executeSerially = true) + { + return CallApi(service, requestUri, (Uri uri) => ConstructUnityFormPost(uri, form), executeSerially); + } + + public static IPromise Post(Service service, string requestUri, HTTPFormBase form, bool executeSerially = true) + { + return CallApi(service, requestUri, (Uri uri) => ConstructUnityFormPost(uri, form), executeSerially); + } + + public static IPromise Post(Service service, string requestUri, HTTPMultiPartForm multiPartForm, bool executeSerially = true) + { + return Post(service, requestUri, (HTTPFormBase)multiPartForm, executeSerially); + } + + public static IPromise Post(Service service, string requestUri, string json, bool executeSerially = true) + { + return CallApi(service, requestUri, (Uri uri) => ConstructJsonFormPost(uri, json), executeSerially); + } + + public static IPromise Post(Service service, string requestUri, TRequest request, bool executeSerially = true) where TRequest : IRecNetRequestObject + { + return CallApi(service, requestUri, (Uri uri) => ConstructRecNetObjectPost(uri, request), executeSerially); + } + + public static IPromise Post(string requestUri, Dictionary form, bool executeSerially = true) + { + return Post(Service.API, requestUri, form, executeSerially); + } + + public static IPromise Post(string requestUri, WWWForm form, bool executeSerially = true) + { + return Post(Service.API, requestUri, form, executeSerially); + } + + public static IPromise Post(string requestUri, HTTPFormBase form, bool executeSerially = true) + { + return Post(Service.API, requestUri, form, executeSerially); + } + + public static IPromise Post(string requestUri, HTTPMultiPartForm multiPartForm, bool executeSerially = true) + { + return Post(requestUri, (HTTPFormBase)multiPartForm, executeSerially); + } + + public static IPromise Post(string requestUri, string json, bool executeSerially = true) + { + return Post(Service.API, requestUri, json, executeSerially); + } + + public static IPromise Post(string requestUri, TRequest request, bool executeSerially = true) where TRequest : IRecNetRequestObject + { + return Post(Service.API, requestUri, request, executeSerially); + } + + public static IPromise PostUsingLoginQueue(Service service, string requestUri, string json) + { + return CallApi(service, requestUri, (Uri uri) => ConstructJsonFormPost(uri, json), loginQueue); + } + + public static IPromise PostUsingBackgroundImageUploadQueue(Service service, string requestUri, HTTPFormBase form) + { + return CallApi(service, requestUri, (Uri uri) => ConstructUnityFormPost(uri, form), imageQueue); + } + + public static IPromise PostUsingLoginQueue(string requestUri, string json) + { + return PostUsingLoginQueue(Service.API, requestUri, json); + } + + public static IPromise PostUsingBackgroundImageUploadQueue(string requestUri, HTTPFormBase form) + { + return PostUsingBackgroundImageUploadQueue(Service.API, requestUri, form); + } + + public static IEnumerator ConnectToRecNet(ApiCallback callback) + { + string url = "https://ns.rec.net/?v=2"; + string log = "Connecting to RecNet"; + + if (!string.IsNullOrEmpty(RecNetEnv)) + { + url = $"{url}&e={Uri.EscapeDataString(RecNetEnv)}"; + log = $"{log} {RecNetEnv}"; + } + + Debug.Log(log); + + HTTPRequest request = new HTTPRequest(new Uri(url), HTTPMethods.Get); + yield return request.Send(); + + string error = GetError(request, true); + if (!string.IsNullOrEmpty(error)) + { + request = new HTTPRequest(new Uri("http://www.google.com/generate_204"), HTTPMethods.Get); + yield return request.Send(); + if (request.Response != null && request.Response.StatusCode == 204) + { + Debug.LogError($"RecNet name server query failed (error code: summer): {error}"); + SafeInvoke(callback, "Failed to connect to RecNet (error code: summer)"); + } + else + { + Debug.LogError($"Network connectivity test failed: {GetError(request, true)}"); + SafeInvoke(callback, "No internet connection"); + } + yield break; + } + try + { + NameServerResponse nameServerResponse = JsonUtility.FromJson(request.Response.DataAsText); + + if (nameServerResponse.RecNetStatus != null) + { + Debug.LogError(nameServerResponse.RecNetStatus); + SafeInvoke(callback, nameServerResponse.RecNetStatus); + yield break; + } + + TrySetServiceUri(Service.Auth, nameServerResponse.Auth); + TrySetServiceUri(Service.API, nameServerResponse.API); + TrySetServiceUri(Service.WWW, nameServerResponse.WWW); + TrySetServiceUri(Service.Commerce, nameServerResponse.Commerce); + TrySetServiceUri(Service.Matchmaking, nameServerResponse.API); + TrySetServiceUri(Service.Notifications, nameServerResponse.Notifications); + TrySetServiceUri(Service.Images, nameServerResponse.Images); + TrySetServiceUri(Service.CDN, nameServerResponse.CDN); + TrySetServiceUri(Service.Storage, nameServerResponse.API); + TrySetServiceUri(Service.Chat, nameServerResponse.API); + TrySetServiceUri(Service.Leaderboard, nameServerResponse.API); + TrySetServiceUri(Service.Accounts, nameServerResponse.API); + TrySetServiceUri(Service.Rooms, nameServerResponse.API); + + Service[] servicesRequiringAuth = new[] + { + Service.Auth, + Service.API, + Service.Commerce, + Service.Matchmaking, + Service.Notifications, + //Service.Images, + //Service.CDN, + Service.Storage, + Service.Chat, + Service.Leaderboard, + Service.Accounts, + Service.Rooms + }; + + HostsRequiringRecNetAccessToken = new HashSet(); + + foreach (Service service in servicesRequiringAuth) + if (ServiceUris.ContainsKey(service) && ServiceUris[service] != null) + HostsRequiringRecNetAccessToken.Add(ServiceUris[service].Host); + } + catch (Exception exception) + { + Debug.LogException(exception); + SafeInvoke(callback, "Failed to connect to RecNet (error code: locker)"); + yield break; + } + + var requestUri = $"api/versioncheck/v3?v={Uri.EscapeDataString(BuildSettings.Version)}&p={Convert.ToInt32(PlatformManager.Instance.CurrentPlatform)}"; + + yield return Get(requestUri).ExpectResponse().Error(delegate (string err) + { + Debug.LogError($"RecNet version check failed: {err}"); + SafeInvoke(callback, "Failed to connect to RecNet (error code: student)"); + }).Then(delegate (VersionCheckResponse response) + { + if (!response.ValidVersion) + SafeInvoke(callback, "Rec Room update required"); + + else + SafeInvoke(callback, null); + }); + } + + private static void LazyInit() + { + if (!initialized) + { + HTTPUpdateDelegator.OnBeforeApplicationQuit = delegate + { + Notifications.Disconnect(); + Login.Logout(); + Thread.Sleep(1000); + return true; + }; + + Notifications.Initialize(); + GameSessions.Initialize(); + Images.Initialize(); + Messages.Initialize(); + Moderation.Initialize(); + Presence.Initialize(); + Profiles.Initialize(); + Relationships.Initialize(); + PlayerSubscriptions.Initialize(); + PlayerReporting.Initialize(); + Avatars.Initialize(); + Rooms.Initialize(); + Storefronts.Initialize(); + Consumables.Initialize(); + PlayerEvents.Initialize(); + CommunityBoard.Initialize(); + Chats.Initialize(); + Login.Initialize(); + + GameObject gameObject = new GameObject("RecNetCoreCoroutineObject"); + UnityEngine.Object.DontDestroyOnLoad(gameObject); + gameObject.hideFlags = HideFlags.HideInHierarchy; + + RecNetCore recNetCore = gameObject.AddComponent(); + recNetCore.StartCoroutine(ProcessCallApiQueue(loginQueue)); + recNetCore.StartCoroutine(ProcessCallApiQueue(serialQueue)); + recNetCore.StartCoroutine(PeriodicRefreshLogin()); + + for (int num = 0; num < PARALLEL_WORKER_THREAD_COUNT; num++) + recNetCore.StartCoroutine(ProcessCallApiQueue(parallelQueue)); + + for (int num2 = 0; num2 < PARALLEL_WORKER_THREAD_COUNT; num2++) + recNetCore.StartCoroutine(ProcessCallApiQueue(imageQueue)); + + recNetCore.StartCoroutine(ProcessEventQueue()); + initialized = true; + } + } + + private static void TrySetServiceUri(Service service, string uri) + { + if (!string.IsNullOrEmpty(uri)) + ServiceUris[service] = new Uri(uri); + } + + private static float GetExponentialBackoffTime(int retryCount) + => (retryCount <= 0) ? 0f : ((float)(1 << Mathf.Min(retryCount - 1, MAX_BACKOFF_EXPONENT))); + + private static HTTPRequest ConstructSimpleFormPost(Uri uri, Dictionary form) + { + var request = new HTTPRequest(uri, HTTPMethods.Post); + foreach (KeyValuePair item in form) + request.AddField(item.Key, item.Value); + + return request; + } + + private static HTTPRequest ConstructUnityFormPost(Uri uri, WWWForm form) + { + HTTPRequest hTTPRequest = new HTTPRequest(uri, HTTPMethods.Post); + hTTPRequest.SetFields(form); + return hTTPRequest; + } + + private static HTTPRequest ConstructUnityFormPost(Uri uri, HTTPFormBase form) + { + HTTPRequest hTTPRequest = new HTTPRequest(uri, HTTPMethods.Post); + hTTPRequest.SetForm(form); + return hTTPRequest; + } + + private static HTTPRequest ConstructJsonFormPost(Uri uri, string json) + { + HTTPRequest hTTPRequest = new HTTPRequest(uri, HTTPMethods.Post); + hTTPRequest.SetHeader("Content-Type", "application/json"); + hTTPRequest.RawData = Encoding.UTF8.GetBytes(json); + return hTTPRequest; + } + + private static HTTPRequest ConstructRecNetObjectPost(Uri uri, IRecNetRequestObject request) + { + Dictionary obj = request.Serialize(); + string json = Json.Serialize(obj); + return ConstructJsonFormPost(uri, json); + } + + private static void AddRecNetAuthToken(HTTPRequest request) + { + if (RequiresRecNetAccessToken(request.CurrentUri)) + { + if (Login.AccessToken != null) + request.SetHeader("Authorization", $"Bearer {Login.AccessToken}"); + + request.OnBeforeRedirection += ClearRecNetAuthToken; + } + } + + private static bool ClearRecNetAuthToken(HTTPRequest request, HTTPResponse response, Uri redirectUri) + { + if (!RequiresRecNetAccessToken(redirectUri)) + { + request.RemoveHeader("Authorization"); + } + + return true; + } + + public static Uri GetServiceUri(Service service) + { + ServiceUris.TryGetValue(service, out var uri); + return uri; + } + + private static bool RequiresRecNetAccessToken(Uri uri) => HostsRequiringRecNetAccessToken.Contains(uri.Host); + + private static IEnumerator PeriodicRefreshLogin() + { + WaitForSeconds wait = new WaitForSeconds(REFRESH_LOGIN_INTERVAL); + while (true) + { + yield return wait; + if (Login.AccessToken != null) + { + yield return Login.RefreshLogin(); + } + } + } + + public static void SetLocalProfileId(long profileId) + { + LocalProfileId = profileId; + } + + private static IPromise CallApi(Service service, string uri, ConstructRequestCallback constructRequest, bool executeSerially) + { + Queue queue = executeSerially ? serialQueue : parallelQueue; + return CallApi(service, uri, constructRequest, queue); + } + + private static IPromise CallApi(Service service, string uri, ConstructRequestCallback constructRequest, Queue queue) + { + LazyInit(); + QueuedApiCall queuedApiCall = new QueuedApiCall(service, uri, constructRequest); + queue.Enqueue(queuedApiCall); + return queuedApiCall.Promise; + } + + private static IPromise CallApi(Service service, string uri, AsyncConstructRequestCallback asyncConstructRequest, Queue queue) + { + LazyInit(); + QueuedApiCall queuedApiCall = new QueuedApiCall(service, uri, asyncConstructRequest); + queue.Enqueue(queuedApiCall); + return queuedApiCall.Promise; + } + + private static IEnumerator ProcessCallApiQueue(Queue queue) + { + int consecutiveRetries = 0; + float previousErrorTime = 0f; + + while (true) + { + yield return new WaitUntil(() => queue.Count > 0); + if (queue.Count == 0) + { + continue; + } + + QueuedApiCall apiCall = queue.Dequeue(); + + Uri baseUri = GetServiceUri(apiCall.Service); + if (baseUri == null) + { + continue; + } + + Uri uri = new Uri(baseUri, apiCall.Uri); + + HTTPRequest request = null; + + for (int retry = 0; retry < API_RETRIES; retry++) + { + if (request != null) + { + request.Dispose(); + request = null; + } + + if (consecutiveRetries > 0) + { + float exponentialBackoff = GetExponentialBackoffTime(consecutiveRetries); + float timeToWait = exponentialBackoff - (Time.realtimeSinceStartup - previousErrorTime); + if (timeToWait > 0f) + { + yield return new WaitForSecondsRealtime(timeToWait); + } + } + + if (apiCall.ConstructRequest != null) + { + try + { + request = apiCall.ConstructRequest(uri); + if (queue == imageQueue) + { + request.Priority = -1; + } + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + else + { + yield return apiCall.AsyncConstructRequest(uri, delegate (HTTPRequest result) + { + request = result; + }); + } + + if (request == null) + { + break; + } + + AddRecNetAuthToken(request); + yield return request.Send(); + + if (request.Response == null || request.Response.StatusCode == 429 || + (request.Response.StatusCode >= 500 && request.Response.StatusCode < 600)) + { + consecutiveRetries++; + previousErrorTime = Time.realtimeSinceStartup; + continue; + } + + if (request.Response != null && request.Response.StatusCode == 401) + { + consecutiveRetries++; + previousErrorTime = Time.realtimeSinceStartup; + } + else + { + consecutiveRetries = 0; + } + break; + } + + try + { + string error = GetError(request, false); + if (error == null) + { + apiCall.Promise.Complete(request.Response); + } + else + { + apiCall.Promise.Error(error); + } + } + catch (Exception exception2) + { + Debug.LogException(exception2); + } + + request?.Dispose(); + request = null; + } + } + + public static string GetError(HTTPRequest request, bool handleHttpStatusCodes) + { + if (request == null) + { + return "Failed to construct Web request"; + } + switch (request.State) + { + case HTTPRequestStates.Aborted: + return "Aborted"; + case HTTPRequestStates.ConnectionTimedOut: + return "Connection timed out"; + case HTTPRequestStates.TimedOut: + return "Response timed out"; + case HTTPRequestStates.Error: + Debug.LogException(request.Exception); + return (request.Exception == null) ? "Unspecified error" : request.Exception.Message; + case HTTPRequestStates.Finished: + return (!handleHttpStatusCodes || request.Response.StatusCode < 400) ? null : ("HTTP Error " + request.Response.StatusCode); + default: + Debug.LogError("HTTPRequest is in an invalid state: " + request.State); + return "Unexpected state"; + } + } + + public static void SafeInvoke(ApiCallback callback, string error) + { + try + { + if (callback != null) + { + callback(error); + } + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + + public static void SafeInvoke(ApiCallback callback, string error, T response) + { + try + { + if (callback != null) + { + callback(error, response); + } + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + + public static void DispatchOnUnityThread(Action callback) + { + lock (eventQueue) + { + eventQueue.Enqueue(callback); + } + } + + public static IEnumerator RunOnBackgroundThread(Action callback) + { + bool complete = false; + Thread thread = new Thread((ThreadStart)delegate + { + try + { + callback(); + } + catch (Exception ex) + { + Exception ex2 = ex; + Exception ex3 = ex2; + DispatchOnUnityThread(delegate + { + Debug.LogException(ex3); + }); + } + finally + { + complete = true; + } + }); + thread.Start(); + return new WaitUntil(() => complete); + } + + private static IEnumerator ProcessEventQueue() + { + while (true) + { + yield return new WaitUntil(() => eventQueue.Count > 0); + Action callback; + lock (eventQueue) + { + callback = eventQueue.Dequeue(); + } + try + { + callback(); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + } + } +} \ No newline at end of file