diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 7e20396ec..e4d92fdde 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,7 +10,7 @@ assignees: '' **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]. Also consider if this is an issue that the plugin handles, or something that the Epic Online Services -SDK handles, E.G. "It would be nice if the EOS ran on the Plan 9 Operating System". Such requests are usually best posted to the Epic's [forum](https://eoshelp.epicgames.com/s/). +SDK handles, E.G. "It would be nice if the EOS ran on the Plan 9 Operating System". Such requests are usually best posted to the Epic's [forum](https://eoshelp.epicgames.com/). **Describe the solution you'd like** A clear and concise description of what you want to happen. diff --git a/.github/workflows/check-markdown-links.yml b/.github/workflows/check-markdown-links.yml new file mode 100644 index 000000000..a962bbe67 --- /dev/null +++ b/.github/workflows/check-markdown-links.yml @@ -0,0 +1,24 @@ +name: Check Markdown Links + +on: + push: + branches: ["feat/documentation-link-checker"] + #paths: + # - '**/*.md' + pull_request: + paths: + - '**/*.md' + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: pip install requests tqdm beautifulsoup4 + - name: Run link checker + run: python tools/scripts/check_links.py \ No newline at end of file diff --git a/.gitignore b/.gitignore index 683b80225..617df5ae3 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,10 @@ # Autogenerated VS/MD/Consulo solution and project files ExportedObj/ .consulo/ -*.csproj + +# Only exclude csproj files in the root of the repository +/*.csproj + *.unityproj *.sln *.suo diff --git a/Assets/Plugins/Android/Core/AndroidConfig.cs b/Assets/Plugins/Android/Core/AndroidConfig.cs index 446e421c3..58501ce4d 100644 --- a/Assets/Plugins/Android/Core/AndroidConfig.cs +++ b/Assets/Plugins/Android/Core/AndroidConfig.cs @@ -22,9 +22,33 @@ namespace PlayEveryWare.EpicOnlineServices { - // Flags specifically for Android + // Flags specifically for Android. Note that labels for the baser + // PlatformConfig need to be specified here. + [ConfigGroup("Android Config", new[] + { + "Android-Specific Options", + "Deployment", + "Flags", + "Tick Budgets", + "Overlay Options" + }, false)] public class AndroidConfig : PlatformConfig { + [ConfigField("Google Login Client ID", + ConfigFieldType.Text, + "Get your project's Google Login Client ID from the " + + "Google Cloud dashboard", + 0, + "https://console.cloud.google.com/apis/dashboard")] + public string GoogleLoginClientID; + + [ConfigField("Google Login Nonce", + ConfigFieldType.Text, + "Use a nonce to improve sign-ing security on Android.", + 0, + "https://developer.android.com/google/play/integrity/classic#nonce")] + public string GoogleLoginNonce; + static AndroidConfig() { RegisterFactory(() => new AndroidConfig()); diff --git a/Assets/Plugins/Android/login.java b/Assets/Plugins/Android/login.java new file mode 100644 index 000000000..174dcfb07 --- /dev/null +++ b/Assets/Plugins/Android/login.java @@ -0,0 +1,121 @@ +/* +* Copyright (c) 2024 PlayEveryWare +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +package com.playeveryware.googlelogin; + +import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption; +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; +import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException; + +import androidx.credentials.GetCredentialRequest; +import androidx.credentials.GetCredentialResponse; +import androidx.credentials.exceptions.GetCredentialException; +import androidx.credentials.Credential; +import androidx.credentials.CustomCredential; +import androidx.credentials.CredentialManager; +import androidx.credentials.CredentialManagerCallback; + +import android.app.Activity; +import android.content.Context; +import android.os.CancellationSignal; +import android.util.Log; + +import java.util.concurrent.Executors; + + +public class login extends Activity +{ + private String name; + private String token; + private static login instance; + + public login() + { + this.instance = this; + } + public static login instance() + { + if(instance == null) + { + instance = new login(); + } + return instance; + } + + public String getResultName() + { + return name; + } + public String getResultIdToken() + { + return token; + } + + public void SignInWithGoogle(String clientID, String nonce, Context context, CredentialManagerCallback callback) + { + GetSignInWithGoogleOption signInWithGoogleOption = new GetSignInWithGoogleOption.Builder(clientID) + .setNonce(nonce) + .build(); + + GetCredentialRequest request = new GetCredentialRequest.Builder() + .addCredentialOption(signInWithGoogleOption) + .build(); + + CredentialManager credentialManager = CredentialManager.create(this); + credentialManager.getCredentialAsync(context,request,new CancellationSignal(),Executors.newSingleThreadExecutor(),callback); + } + + public void handleFailure(GetCredentialException e) + { + Log.e("Unity", "Received an invalid google id token response", e); + } + + public void handleSignIn(GetCredentialResponse result) + { + Credential credential = result.getCredential(); + + if (credential instanceof CustomCredential) + { + if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) + { + try + { + GoogleIdTokenCredential googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData()); + name = googleIdTokenCredential.getDisplayName(); + token = googleIdTokenCredential.getIdToken(); + } + catch (Exception e) + { + if (e instanceof GoogleIdTokenParsingException) + { + Log.e("Unity", "Received an invalid google id token response", e); + } + else + { + Log.e("Unity", "Some exception", e); + } + } + } + } + } +} + diff --git a/Assets/Plugins/Android/login.java.meta b/Assets/Plugins/Android/login.java.meta new file mode 100644 index 000000000..f820db864 --- /dev/null +++ b/Assets/Plugins/Android/login.java.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 89ca82eb8db76ec47b323edb4fe70f92 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Android/mainTemplate.gradle b/Assets/Plugins/Android/mainTemplate.gradle index a699ead06..5217abc98 100644 --- a/Assets/Plugins/Android/mainTemplate.gradle +++ b/Assets/Plugins/Android/mainTemplate.gradle @@ -4,6 +4,9 @@ apply plugin: 'com.android.library' **APPLY_PLUGINS** dependencies { + implementation 'androidx.credentials:credentials-play-services-auth:1.3.0' + implementation 'androidx.credentials:credentials:1.3.0' + implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation fileTree(dir: 'libs', include: ['*.jar']) **DEPS**} diff --git a/Assets/Plugins/Linux/Core/LinuxConfig.cs b/Assets/Plugins/Linux/Core/LinuxConfig.cs index 349a23fcc..b83e0cc20 100644 --- a/Assets/Plugins/Linux/Core/LinuxConfig.cs +++ b/Assets/Plugins/Linux/Core/LinuxConfig.cs @@ -22,10 +22,16 @@ namespace PlayEveryWare.EpicOnlineServices { - using System; - - // Flags specifically for Linux - [Serializable] + // Flags specifically for Linux. Note that labels for the baser + // PlatformConfig need to be specified here. + [ConfigGroup("Linux Config", new[] + { + "Linux-Specific Options", + "Deployment", + "Flags", + "Tick Budgets", + "Overlay Options" + }, false)] public class LinuxConfig : PlatformConfig { static LinuxConfig() diff --git a/Assets/Plugins/Source/Editor/Build/BuildRunner.cs b/Assets/Plugins/Source/Editor/Build/BuildRunner.cs index 5052a33cb..469d56fda 100644 --- a/Assets/Plugins/Source/Editor/Build/BuildRunner.cs +++ b/Assets/Plugins/Source/Editor/Build/BuildRunner.cs @@ -22,7 +22,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Build { - using Config; + using PlayEveryWare.EpicOnlineServices; using Utility; using UnityEditor.Build; using UnityEditor.Build.Reporting; @@ -64,10 +64,9 @@ public static PlatformSpecificBuilder Builder /// The pre-process build report. public void OnPreprocessBuild(BuildReport report) { - // Set the current platform that is being built against - if (PlatformManager.TryGetPlatform(report.summary.platform, out PlatformManager.Platform platform)) + if (ScriptingDefineUtility.IsEOSDisabled(report)) { - PlatformManager.CurrentPlatform = platform; + return; } // Run the static builder's prebuild. @@ -76,7 +75,7 @@ public void OnPreprocessBuild(BuildReport report) #if !DISABLESTEAMWORKS // If we're using Steamworks, then look at the user's Steam configuration file // If the "steamApiInterfaceVersionsArray" is empty, try to set it for the user - SteamConfig config = SteamConfig.Get(); + SteamConfig config = Config.Get(); if (config != null && (config.steamApiInterfaceVersionsArray == null || config.steamApiInterfaceVersionsArray.Count == 0)) { config.steamApiInterfaceVersionsArray = SteamworksUtility.GetSteamInterfaceVersions(); @@ -88,7 +87,7 @@ public void OnPreprocessBuild(BuildReport report) else { UnityEngine.Debug.Log($"BuildRunner: This project is using Steamworks, but has not yet configured the steamApiInterfaceVersionsArray. The builder has automatically configured this field and will now try to save the value."); - config.Write(true, false); + config.Write(true); } } #endif @@ -101,6 +100,11 @@ public void OnPreprocessBuild(BuildReport report) /// The report from the post-process build. public void OnPostprocessBuild(BuildReport report) { + if (ScriptingDefineUtility.IsEOSDisabled(report)) + { + return; + } + // Run the static builder's postbuild s_builder?.PostBuild(report); } diff --git a/Assets/Plugins/Source/Editor/Build/PlatformSpecificBuilder.cs b/Assets/Plugins/Source/Editor/Build/PlatformSpecificBuilder.cs index a496cb527..16d25a3c0 100644 --- a/Assets/Plugins/Source/Editor/Build/PlatformSpecificBuilder.cs +++ b/Assets/Plugins/Source/Editor/Build/PlatformSpecificBuilder.cs @@ -22,6 +22,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Build { + using Common; using Config; using System.Collections.Generic; using System.IO; @@ -241,19 +242,18 @@ private static void ConfigureVersion() /// private static async void AutoSetProductVersion() { - var eosConfig = await Config.GetAsync(); - var prebuildConfig = await Config.GetAsync(); - var previousProdVer = eosConfig.productVersion; + PrebuildConfig prebuildConfig = await Config.GetAsync(); + ProductConfig productConfig = Config.Get(); - if (prebuildConfig.useAppVersionAsProductVersion) + // If the product version is set by config, or if it already equals the product config, stop here. + if (!prebuildConfig.useAppVersionAsProductVersion || productConfig.ProductVersion == Application.version) { - eosConfig.productVersion = Application.version; + return; } - if (previousProdVer != eosConfig.productVersion) - { - await eosConfig.WriteAsync(true); - } + // Otherwise, set the new product version and write the config. + productConfig.ProductVersion = Application.version; + await productConfig.WriteAsync(); } /// diff --git a/Assets/Plugins/Source/Editor/ConfigEditors/ConfigEditor.cs b/Assets/Plugins/Source/Editor/ConfigEditors/ConfigEditor.cs index b8acba4e9..9aaaf0347 100644 --- a/Assets/Plugins/Source/Editor/ConfigEditors/ConfigEditor.cs +++ b/Assets/Plugins/Source/Editor/ConfigEditors/ConfigEditor.cs @@ -20,11 +20,10 @@ * SOFTWARE. */ + namespace PlayEveryWare.EpicOnlineServices.Editor { using System; - using System.Collections.Generic; - using System.Linq; using System.Reflection; using UnityEditor; using UnityEditor.AnimatedValues; @@ -41,7 +40,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor /// The type of config that this editor is responsible for providing an /// interface to edit for. /// - public class ConfigEditor : IConfigEditor where T : + public class ConfigEditor : IConfigEditor where T : EpicOnlineServices.Config { /// @@ -94,7 +93,7 @@ public class ConfigEditor : IConfigEditor where T : /// collapsed. /// public ConfigEditor( - UnityAction repaintFn = null, + UnityAction repaintFn = null, bool startsExpanded = false) { _expanded = startsExpanded; @@ -104,7 +103,7 @@ public ConfigEditor( if (null != attribute) { - _collapsible = attribute.Collapsible; + _collapsible = attribute.Collapsible; _labelText = attribute.Label; _groupLabels = attribute.GroupLabels; } @@ -158,116 +157,6 @@ protected virtual void OnExpanded(EventArgs e) handler?.Invoke(this, e); } - /// - /// Use reflection to retrieve a collection of fields that have been - /// assigned custom ConfigFieldAttribute attributes, grouping by group, - /// and sorting by group. - /// - /// A collection of config fields. - private static IOrderedEnumerable> GetFieldsByGroup() - { - return typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance) - .Where(field => field.GetCustomAttribute() != null) - .Select(info => (info, info.GetCustomAttribute())) - .GroupBy(r => r.Item2.Group) - .OrderBy(group => group.Key); - } - - /// - /// Using a collection of FieldInfo and ConfigFieldAttribute classes, - /// representing the fields in a Config, determine which field in a - /// given group is the longest, and return that length. - /// - /// - /// A group of fields that have ConfigFieldAttribute and the same group - /// number. - /// - /// - /// The length of the longest label to create - /// - private static float GetMaximumLabelWidth(IEnumerable<(FieldInfo, ConfigFieldAttribute)> group) - { - GUIStyle labelStyle = new(GUI.skin.label); - - float maxWidth = 0f; - foreach (var field in group) - { - string labelText = field.Item2.Label; - - Vector2 labelSize = labelStyle.CalcSize(new GUIContent(labelText)); - if (maxWidth < labelSize.x) - { - maxWidth = labelSize.x; - } - } - - return maxWidth; - } - - /// - /// Render the config fields for the config that has been set to edit. - /// - /// - /// Thrown for types that are not yet implemented. - /// - /// - /// Thrown for types that are not yet implemented, and not accounted for - /// in the switch statement. - /// - protected void RenderConfigFields() - { - foreach (var fieldGroup in GetFieldsByGroup()) - { - float labelWidth = GetMaximumLabelWidth(fieldGroup); - - // If there is a label for the field group, then display it. - if (0 >= fieldGroup.Key && _groupLabels?.Length > fieldGroup.Key) - { - GUILayout.Label(_groupLabels[fieldGroup.Key], EditorStyles.boldLabel); - } - - foreach (var field in fieldGroup) - { - switch (field.FieldDetails.FieldType) - { - case ConfigFieldType.Text: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails, (string)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.FilePath: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails as FilePathFieldAttribute, (string)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.Flag: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails, (bool)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.DirectoryPath: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails as DirectoryPathFieldAttribute, (string)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.Ulong: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails, (ulong)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.Double: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails, (double)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.TextList: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails, (List)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.Uint: - field.FieldInfo.SetValue(config, GUIEditorUtility.RenderInputField(field.FieldDetails, (uint)field.FieldInfo.GetValue(config), labelWidth)); - break; - case ConfigFieldType.Button: - if (GUILayout.Button(field.FieldDetails.Label) && - field.FieldInfo.GetValue(config) is Action onClick) - { - onClick(); - } - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - } - public string GetLabelText() { return _labelText; @@ -289,7 +178,7 @@ public void Load() Task.Run(LoadAsync).GetAwaiter().GetResult(); } - public async Task Save(bool prettyPrint) + public async Task Save(bool prettyPrint = true) { await config.WriteAsync(prettyPrint); } @@ -302,8 +191,8 @@ public virtual void RenderContents() } else { - GUILayout.Label(GetLabelText(), EditorStyles.boldLabel); - RenderConfigFields(); + GUIEditorUtility.RenderSectionHeader(GetLabelText()); + GUIEditorUtility.RenderInputs(ref config); } } @@ -338,7 +227,7 @@ private void RenderCollapsibleContents() if (EditorGUILayout.BeginFadeGroup(_animExpanded.faded)) { EditorGUILayout.BeginVertical(GUI.skin.box); - RenderConfigFields(); + GUIEditorUtility.RenderInputs(ref config); EditorGUILayout.EndVertical(); } EditorGUILayout.EndFadeGroup(); diff --git a/Assets/Plugins/Source/Editor/ConfigEditors/IPlatformConfigEditor.cs b/Assets/Plugins/Source/Editor/ConfigEditors/IPlatformConfigEditor.cs index 8e76420af..9a01e74cb 100644 --- a/Assets/Plugins/Source/Editor/ConfigEditors/IPlatformConfigEditor.cs +++ b/Assets/Plugins/Source/Editor/ConfigEditors/IPlatformConfigEditor.cs @@ -22,6 +22,8 @@ namespace PlayEveryWare.EpicOnlineServices.Editor { + using UnityEngine; + // Interface for allowing adding additional config files to the Config editor public interface IPlatformConfigEditor : IConfigEditor { @@ -33,5 +35,7 @@ public interface IPlatformConfigEditor : IConfigEditor /// True if the platform can be targetted by the Unity Editor. /// bool IsPlatformAvailable(); + + Texture GetPlatformIconTexture(); } } \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/ConfigEditors/PlatformConfigEditor.cs b/Assets/Plugins/Source/Editor/ConfigEditors/PlatformConfigEditor.cs index 1b395c3ea..366e446e1 100644 --- a/Assets/Plugins/Source/Editor/ConfigEditors/PlatformConfigEditor.cs +++ b/Assets/Plugins/Source/Editor/ConfigEditors/PlatformConfigEditor.cs @@ -19,10 +19,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace PlayEveryWare.EpicOnlineServices.Editor { using System.Linq; - using UnityEditor; using UnityEngine; using Utility; @@ -32,9 +32,9 @@ namespace PlayEveryWare.EpicOnlineServices.Editor /// platform. /// /// Intended to be a type accepted by the templated class ConfigHandler. - public abstract class PlatformConfigEditor : ConfigEditor, IPlatformConfigEditor where T : PlatformConfig + public class PlatformConfigEditor : ConfigEditor, IPlatformConfigEditor where T : PlatformConfig { - protected PlatformConfigEditor() + public PlatformConfigEditor() { Load(); @@ -42,57 +42,19 @@ protected PlatformConfigEditor() _labelText = PlatformManager.GetFullName(config.Platform); } - /// - /// Given that most platform configurations allow for override values of a specific subset of the standard - /// options applied to all platforms, the rendering of these options is shared by all PlatformConfigEditor implementations. - /// - /// TODO: Consider the scenario where there are values that need to be overriden for one platform, but not for another. How would this work? - /// NOTE: Currently, all platforms override the same set of values, so this is not currently an issue. - /// - public virtual void RenderOverrides() - { - GUILayout.Label($"{PlatformManager.GetFullName(config.Platform)} Override Configuration Values", - EditorStyles.boldLabel); - - GUIEditorUtility.AssigningFlagTextField("Integrated Platform Management Flags (Separated by '|')", ref config.flags, 345); - - GUIEditorUtility.AssigningFlagTextField("Override Platform Flags (Separated by '|')", ref config.overrideValues.platformOptionsFlags, 250); - - GUIEditorUtility.AssigningFloatToStringField("Override initial button delay for overlay", ref config.overrideValues.initialButtonDelayForOverlay, 250); - - GUIEditorUtility.AssigningFloatToStringField("Override repeat button delay for overlay", ref config.overrideValues.repeatButtonDelayForOverlay, 250); - - // TODO: As far as can be determined, it appears that the following - // values are the only ones within "overrideValues" that are - // actually being used to override the otherwise defined - // values within EOSConfig. Changing the values in the fields - // for which the input fields are rendered above does not - // seem to have any affect. - // - // This is a bug, but is not relevant to the current task as - // of this writing, which is to simply add the field member - // "integratedPlatformManagementFlags" to EOSConfig, and make - // sure that field is editable within the editor. - - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: networkWork", ref config.overrideValues.ThreadAffinity_networkWork); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: storageIO", ref config.overrideValues.ThreadAffinity_storageIO); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: webSocketIO", ref config.overrideValues.ThreadAffinity_webSocketIO); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: P2PIO", ref config.overrideValues.ThreadAffinity_P2PIO); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: HTTPRequestIO", ref config.overrideValues.ThreadAffinity_HTTPRequestIO); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: RTCIO", ref config.overrideValues.ThreadAffinity_RTCIO); - } - public bool IsPlatformAvailable() { return PlatformManager.GetAvailableBuildTargets().Contains(config.Platform); } - public virtual void RenderPlatformSpecificOptions() { } + public Texture GetPlatformIconTexture() + { + return PlatformManager.GetPlatformIcon(config.Platform); + } public override sealed void RenderContents() { - RenderPlatformSpecificOptions(); - RenderOverrides(); + GUIEditorUtility.RenderInputs(ref config); } } } \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Configs/EditorConfig.cs b/Assets/Plugins/Source/Editor/Configs/EditorConfig.cs index f6ff8cbc7..3370bcf94 100644 --- a/Assets/Plugins/Source/Editor/Configs/EditorConfig.cs +++ b/Assets/Plugins/Source/Editor/Configs/EditorConfig.cs @@ -23,30 +23,19 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Config { using EpicOnlineServices.Utility; - using System.IO; - using System.Threading.Tasks; using Config = EpicOnlineServices.Config; /// - /// Used for configurations that are editor-only. - /// Configurations are serialized into JSON strings, and then added to EditorPrefs - /// using their filename as the key. + /// Used for configurations that are editor-only. Configurations are + /// serialized into JSON strings, and then added to EditorPrefs using their + /// filename as the key. /// public abstract class EditorConfig : Config { - protected EditorConfig(string filename) : base(filename, Path.Combine(FileSystemUtility.GetProjectPath(), "etc/config/")) { } - - // NOTE: This compiler block is here because the base class "Config" has - // the WriteAsync function surrounded by the same conditional. -#if UNITY_EDITOR - // Overridden functionality changes the default parameter value for updateAssetDatabase, because EditorConfig - // should not be anywhere within Assets. - public override async Task WriteAsync(bool prettyPrint = true, bool updateAssetDatabase = false) - { - // Override the base function - await base.WriteAsync(prettyPrint, updateAssetDatabase); - } -#endif + protected EditorConfig(string filename) : + base(filename, + FileSystemUtility.CombinePaths( + FileSystemUtility.GetProjectPath(), + "etc/config/")) { } } - } \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Configs/SteamConfig.cs b/Assets/Plugins/Source/Editor/Configs/SteamConfig.cs index 95b2146a5..fd49a9f59 100644 --- a/Assets/Plugins/Source/Editor/Configs/SteamConfig.cs +++ b/Assets/Plugins/Source/Editor/Configs/SteamConfig.cs @@ -24,36 +24,69 @@ namespace PlayEveryWare.EpicOnlineServices { using System; using System.Collections.Generic; - using Editor; + using Editor.Config; using Editor.Utility; + using Epic.OnlineServices.IntegratedPlatform; + using Newtonsoft.Json; using Utility; - [Serializable] - [ConfigGroup("Steam Configuration")] - // TODO: Make SteamConfig derive from EditorConfig, and update the native code - // to properly reference the correct file where appropriate. - public class SteamConfig : EpicOnlineServices.Config + [ConfigGroup("Steam Configuration", new string[] + { + "Steamworks SDK", + })] + public class SteamConfig : EditorConfig { - [ConfigField("Steam Flags", ConfigFieldType.TextList)] - public List flags; - #region These fields are referenced by the native code - [DirectoryPathField("Override Library Path")] + [DirectoryPathField("Override Library Path", + "Path to where you have your Steamworks SDK installed.", 0)] public string overrideLibraryPath; - [ConfigField("Steamworks SDK Major Version", ConfigFieldType.Uint)] + [ConfigField("SDK Major Version", + ConfigFieldType.Uint, + "The major version for the Steamworks SDK you have installed.", 0)] public uint steamSDKMajorVersion; - [ConfigField("Steamworks SDK Minor Version", ConfigFieldType.Uint)] + [ConfigField("SDK Minor Version", + ConfigFieldType.Uint, + "The minor version for the Steamworks SDK you have installed.", 0)] public uint steamSDKMinorVersion; - [ConfigField("Steamworks Interface Versions", ConfigFieldType.TextList)] + [ConfigField("Steamworks Interface Versions", ConfigFieldType.TextList, null, 0)] public List steamApiInterfaceVersionsArray; + /// + /// Used to store integrated platform management flags. + /// + [ConfigField("Integrated Platform Management Flags", + ConfigFieldType.Enum, "Integrated Platform Management " + + "Flags for platform specific options.", + 1, "https://dev.epicgames.com/docs/api-ref/enums/eos-e-integrated-platform-management-flags")] + [JsonConverter(typeof(ListOfStringsToIntegratedPlatformManagementFlags))] + public IntegratedPlatformManagementFlags integratedPlatformManagementFlags; + + // This property exists to maintain backwards-compatibility with + // previous versions of the config json structures. + [JsonProperty] // Mark it so that it gets read + [JsonIgnore] // Ignore so that it does not get written + [Obsolete("This property is deprecated. Use the property integratedPlatformManagementFlags instead.")] + [JsonConverter(typeof(ListOfStringsToIntegratedPlatformManagementFlags))] + public IntegratedPlatformManagementFlags flags + { + get + { + return integratedPlatformManagementFlags; + } + set + { + integratedPlatformManagementFlags = value; + } + } + #endregion - [ButtonField("Update from Steamworks.NET")] + [ButtonField("Update from Steamworks.NET", "Click here to try and import the SDK versions from the indicated Steamworks SDK Library referenced above", 0)] + [JsonIgnore] public Action UpdateFromSteamworksNET; static SteamConfig() @@ -61,7 +94,7 @@ static SteamConfig() RegisterFactory(() => new SteamConfig()); } - protected SteamConfig() : base("eos_steam_config.json") + protected SteamConfig() : base("eos_plugin_steam_config.json") { UpdateFromSteamworksNET = () => { diff --git a/Assets/Plugins/Source/Editor/EditorWindows/CheckDeploymentWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/CheckDeploymentWindow.cs index 83002aa79..a40d384b3 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/CheckDeploymentWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/CheckDeploymentWindow.cs @@ -47,7 +47,7 @@ public class CheckDeploymentWindow : EOSEditorWindow public CheckDeploymentWindow() : base("Deployment Checker") { } - [MenuItem("Tools/EOS Plugin/Check Deployment")] + [MenuItem("EOS Plugin/Advanced/Check Deployment")] public static void ShowWindow() { GetWindow(); diff --git a/Assets/Plugins/Source/Editor/EditorWindows/CreatePackageWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/CreatePackageWindow.cs index 39ab8b2f2..d1597fcae 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/CreatePackageWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/CreatePackageWindow.cs @@ -72,7 +72,7 @@ public CreatePackageWindow() : base("Create Package") { } #endregion - [MenuItem("Tools/EOS Plugin/Create Package")] + [MenuItem("EOS Plugin/Advanced/Create Package")] public static void ShowWindow() { GetWindow(); @@ -143,7 +143,7 @@ protected override void RenderWindow() GUILayout.Space(10f); - GUIEditorUtility.RenderFoldout(ref _showAdvanced, "Hide Advanced Options", "Show Advanced Options", RenderAdvanced); + _showAdvanced = GUIEditorUtility.RenderFoldout(_showAdvanced, "Hide Advanced Options", "Show Advanced Options", RenderAdvanced); GUILayout.Space(10f); @@ -269,7 +269,7 @@ protected void RenderAdvanced() if (jsonPackageFile != _packagingConfig.pathToJSONPackageDescription) { _packagingConfig.pathToJSONPackageDescription = jsonPackageFile; - _packagingConfig.Write(true, false); + _packagingConfig.Write(true); } GUIEditorUtility.AssigningBoolField("Clean target directory", ref _cleanBeforeCreate, 150f, diff --git a/Assets/Plugins/Source/Editor/EditorWindows/EOSPluginSettingsWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/EOSPluginSettingsWindow.cs index 14cbf771e..1a32b3326 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/EOSPluginSettingsWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/EOSPluginSettingsWindow.cs @@ -20,20 +20,18 @@ * SOFTWARE. */ -using System; -using System.IO; -using UnityEditor; -using UnityEngine; -using System.Collections.Generic; +#if !EOS_DISABLE namespace PlayEveryWare.EpicOnlineServices.Editor.Windows { + using System; + using System.IO; + using UnityEditor; + using UnityEngine; + using System.Collections.Generic; using Config; using System.Linq; using System.Threading.Tasks; - using UnityEditor.AnimatedValues; - using Utility; - using Config = EpicOnlineServices.Config; /// /// Creates the view for showing the eos plugin editor config values. @@ -64,7 +62,7 @@ public static SettingsProvider CreateSettingsProvider() return provider; } - [MenuItem("Tools/EOS Plugin/Plugin Configuration")] + [MenuItem("EOS Plugin/Plugin Configuration", priority = 2)] public static void ShowWindow() { var window = GetWindow(); @@ -164,4 +162,6 @@ private void Save() AssetDatabase.Refresh(); } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs index eb0c0a895..d4f9322bc 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs @@ -1,405 +1,190 @@ /* -* Copyright (c) 2021 PlayEveryWare -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// Uncomment the following line to see all platforms, even ones that are not +// available +#define DEBUG_SHOW_UNAVAILABLE_PLATFORMS -//#define ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE +#if !EOS_DISABLE namespace PlayEveryWare.EpicOnlineServices.Editor.Windows { #if !EOS_DISABLE using Epic.OnlineServices.UI; #endif - using PlayEveryWare.EpicOnlineServices.Extensions; using PlayEveryWare.EpicOnlineServices.Utility; using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Threading.Tasks; - using UnityEditor; + using System.Collections.Generic; + using System.Threading.Tasks; + using UnityEditor; using UnityEngine; - using Utility; - using Config = EpicOnlineServices.Config; - using Random = System.Random; + /// + /// Creates the view for showing the eos plugin editor config values. + /// [Serializable] - public class EOSSettingsWindow : EOSEditorWindow + public class NEW_EOSSettingsWindow : EOSEditorWindow { - private List platformSpecificConfigEditors; - - private static readonly string ConfigDirectory = Path.Combine("Assets", "StreamingAssets", "EOS"); - - int toolbarInt; - string[] toolbarTitleStrings; - - EOSConfig mainEOSConfigFile; - -#if ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE - string eosGeneratedCFilePath = ""; -#endif - bool prettyPrint; - - public EOSSettingsWindow() : base("EOS Configuration") + /// + /// The editor for the product information that is shared across all + /// platforms (represents information that is common to all + /// circumstances). + /// + private ConfigEditor _productConfigEditor = new(); + + /// + /// Stores the config editors for each of the platforms. + /// + private readonly IList _platformConfigEditors = new List(); + + /// + /// Contains the GUIContent that represents the set of tabs that contain + /// platform icons and platform text (this is not the tab _content_). + /// + private GUIContent[] _platformTabs; + + /// + /// The tab that is currently selected. + /// + private int _selectedTab = -1; + + /// + /// The style to apply to the platform tabs. + /// + private static GUIStyle _platformTabsStyle; + + /// + /// The style to apply to the platform tabs, uses lazy initialization. + /// + private static GUIStyle TAB_STYLE => _platformTabsStyle ??= new(GUI.skin.button) { - } + fontSize = 14, + padding = new RectOffset(10, 10, 10, 10), + alignment = TextAnchor.MiddleCenter, + fixedHeight = 40 + }; + + public NEW_EOSSettingsWindow() : base("EOS Configuration") { } - [MenuItem("Tools/EOS Plugin/EOS Configuration")] + [MenuItem("EOS Plugin/EOS Configuration", priority = 1)] public static void ShowWindow() { - var window = GetWindow(); + var window = GetWindow(); window.SetIsEmbedded(false); } - [SettingsProvider] - public static SettingsProvider CreateProjectSettingsProvider() - { - var settingsWindow = CreateInstance(); - string[] keywords = {"Epic", "EOS", "Online", "Services", "PlayEveryWare"}; - // mark the editor window as being embedded, so it skips auto formatting stuff. - settingsWindow.SetIsEmbedded(true); - var provider = new SettingsProvider($"Preferences/{settingsWindow.WindowTitle}", SettingsScope.Project) - { - label = settingsWindow.WindowTitle, - keywords = keywords, - guiHandler = searchContext => - { - settingsWindow.OnGUI(); - } - }; - - return provider; - } - - private string GenerateEOSGeneratedFile(EOSConfig aEOSConfig) - { - return string.Format( - String.Join("\n", "#define EOS_PRODUCT_NAME \"{0}\"", "#define EOS_PRODUCT_VERSION \"{1}\"", - "#define EOS_SANDBOX_ID \"{2}\"", "#define EOS_PRODUCT_ID \"{3}\"", - "#define EOS_DEPLOYMENT_ID \"{4}\"", "#define EOS_CLIENT_SECRET \"{5}\"", - "#define EOS_CLIENT_ID \"{6}\""), aEOSConfig.productName, - aEOSConfig.productVersion, - aEOSConfig.productID, - aEOSConfig.sandboxID, - aEOSConfig.deploymentID, - aEOSConfig.clientSecret, - aEOSConfig.clientID) + - @" -_WIN32 || _WIN64 -#define PLATFORM_WINDOWS 1 -#endif - -#if _WIN64 -#define PLATFORM_64BITS 1 -#else -#define PLATFORM_32BITS 1 -#endif - - extern ""C"" __declspec(dllexport) char* __stdcall GetConfigAsJSONString() -{ - return ""{"" - ""productName:"" EOS_PRODUCT_NAME "","" - ""productVersion: "" EOS_PRODUCT_VERSION "","" - ""productID: "" EOS_PRODUCT_ID "","" - ""sandboxID: "" EOS_SANDBOX_ID "","" - ""deploymentID: "" EOS_DEPLOYMENT_ID "","" - ""clientSecret: "" EOS_CLIENT_SECRET "","" - ""clientID: "" EOS_CLIENT_ID - - ""}"" - ; - }"; - } - protected override async Task AsyncSetup() { - if (!Directory.Exists(ConfigDirectory)) - { - Directory.CreateDirectory(ConfigDirectory); - } - - mainEOSConfigFile = await Config.GetAsync(); - - platformSpecificConfigEditors ??= new List(); - var configEditors = ReflectionUtility.CreateInstancesOfDerivedGenericClasses(typeof(PlatformConfigEditor<>)); - List toolbarStrings = new(new[] { "Main" }); - foreach (IPlatformConfigEditor editor in configEditors.Cast()) - { - // If the platform for the editor is not available, then do not - // display the editor for it. - if (!editor.IsPlatformAvailable()) - continue; - - toolbarStrings.Add(editor.GetLabelText()); - - platformSpecificConfigEditors.Add(editor); - } - - toolbarTitleStrings = toolbarStrings.ToArray(); - - await base.AsyncSetup(); - } - - private async Task Save(bool usePrettyFormat) - { - await mainEOSConfigFile.WriteAsync(usePrettyFormat); - - foreach (var platformSpecificConfigEditor in platformSpecificConfigEditors) - { - await platformSpecificConfigEditor.Save(usePrettyFormat); - } - -#if ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE - string generatedCFile = GenerateEOSGeneratedFile(mainEOSConfigFile.Data); - File.WriteAllText(Path.Combine(eosGeneratedCFilePath, "EOSGenerated.c"), generatedCFile); -#endif - - AssetDatabase.SaveAssets(); - AssetDatabase.Refresh(); - } - - private void OnDefaultGUI() - { - GUILayout.Label("Epic Online Services", EditorStyles.boldLabel); - - float originalLabelWidth = EditorGUIUtility.labelWidth; - - EditorGUIUtility.labelWidth = 200; - - // TODO: Id the Product Name userfacing? If so, we need loc - GUIEditorUtility.AssigningTextField("Product Name", ref mainEOSConfigFile.productName, - tooltip: "Product Name defined in the EOS Development Portal"); - - // TODO: bool to take product version form application version; should be automatic? - GUIEditorUtility.AssigningTextField("Product Version", ref mainEOSConfigFile.productVersion, - tooltip: "Version of Product"); - GUIEditorUtility.AssigningTextField("Product ID", ref mainEOSConfigFile.productID, - tooltip: "Product ID defined in the EOS Development Portal"); - GUIEditorUtility.AssigningTextField("Sandbox ID", ref mainEOSConfigFile.sandboxID, - tooltip: "Sandbox ID defined in the EOS Development Portal"); - GUIEditorUtility.AssigningTextField("Deployment ID", ref mainEOSConfigFile.deploymentID, - tooltip: "Deployment ID defined in the EOS Development Portal"); + await _productConfigEditor.LoadAsync(); - GUIEditorUtility.AssigningBoolField("Is Server", ref mainEOSConfigFile.isServer, - tooltip: "Set to 'true' if the application is a dedicated game serve"); - - EditorGUILayout.LabelField("Sandbox Deployment Overrides"); - if (mainEOSConfigFile.sandboxDeploymentOverrides == null) - { - mainEOSConfigFile.sandboxDeploymentOverrides = new List(); - } - - for (int i = 0; i < mainEOSConfigFile.sandboxDeploymentOverrides.Count; ++i) + List tabContents = new(); + int tabIndex = 0; + foreach (PlatformManager.Platform platform in Enum.GetValues(typeof(PlatformManager.Platform))) { - EditorGUILayout.BeginHorizontal(); - GUIEditorUtility.AssigningTextField("Sandbox ID", - ref mainEOSConfigFile.sandboxDeploymentOverrides[i].sandboxID, - tooltip: "Deployment ID will be overridden when Sandbox ID is set to this", labelWidth: 70); - mainEOSConfigFile.sandboxDeploymentOverrides[i].sandboxID = - mainEOSConfigFile.sandboxDeploymentOverrides[i].sandboxID.Trim(); - GUIEditorUtility.AssigningTextField("Deployment ID", - ref mainEOSConfigFile.sandboxDeploymentOverrides[i].deploymentID, - tooltip: "Deployment ID to use for override", labelWidth: 90); - mainEOSConfigFile.sandboxDeploymentOverrides[i].deploymentID = - mainEOSConfigFile.sandboxDeploymentOverrides[i].deploymentID.Trim(); - if (GUILayout.Button("Remove", GUILayout.MaxWidth(70))) + // This makes sure that the currently selected tab (upon first loading the window) is always the current platform. + if (_selectedTab != -1 || platform == PlatformManager.CurrentPlatform) { - mainEOSConfigFile.sandboxDeploymentOverrides.RemoveAt(i); + _selectedTab = tabIndex; } - EditorGUILayout.EndHorizontal(); - } - - if (GUILayout.Button("Add", GUILayout.MaxWidth(100))) - { - mainEOSConfigFile.sandboxDeploymentOverrides.Add(new SandboxDeploymentOverride()); - } - - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: networkWork", - ref mainEOSConfigFile.ThreadAffinity_networkWork, - tooltip: "(Optional) Specifies thread affinity for network management that is not IO"); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: storageIO", - ref mainEOSConfigFile.ThreadAffinity_storageIO, - tooltip: "(Optional) Specifies affinity for threads that will interact with a storage device"); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: webSocketIO", - ref mainEOSConfigFile.ThreadAffinity_webSocketIO, - tooltip: "(Optional) Specifies affinity for threads that generate web socket IO"); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: P2PIO", - ref mainEOSConfigFile.ThreadAffinity_P2PIO, - tooltip: - "(Optional) Specifies affinity for any thread that will generate IO related to P2P traffic and management"); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: HTTPRequestIO", - ref mainEOSConfigFile.ThreadAffinity_HTTPRequestIO, - tooltip: "(Optional) Specifies affinity for any thread that will generate http request IO"); - GUIEditorUtility.AssigningULongToStringField("Thread Affinity: RTCIO", - ref mainEOSConfigFile.ThreadAffinity_RTCIO, - tooltip: - "(Optional) Specifies affinity for any thread that will generate IO related to RTC traffic and management"); - - string timeBudgetAsString = ""; - - if (mainEOSConfigFile.tickBudgetInMilliseconds != 0) - { - timeBudgetAsString = mainEOSConfigFile.tickBudgetInMilliseconds.ToString(); - } - - GUIEditorUtility.AssigningTextField("Time Budget in milliseconds", ref timeBudgetAsString, - tooltip: "(Optional) Define the maximum amount of execution time the EOS SDK can use each frame"); - - if (timeBudgetAsString.Length != 0) - { - try + if (!PlatformManager.TryGetConfigType(platform, out Type configType) || null == configType) { - mainEOSConfigFile.tickBudgetInMilliseconds = Convert.ToUInt32(timeBudgetAsString, 10); - } - catch - { - Debug.LogWarning($"{nameof(EOSSettingsWindow)} ({nameof(OnDefaultGUI)}): {nameof(mainEOSConfigFile.tickBudgetInMilliseconds)} must be convertable to int, but string could not be parsed. The provided string is \"{timeBudgetAsString}\". This value is ignored."); + continue; } - } - else - { - mainEOSConfigFile.tickBudgetInMilliseconds = 0; - } - string taskNetworkTimeoutSecondsAsString = ""; + Type constructedType = + typeof(PlatformConfigEditor<>).MakeGenericType(configType); - if (mainEOSConfigFile.taskNetworkTimeoutSeconds != 0) - { - taskNetworkTimeoutSecondsAsString = mainEOSConfigFile.taskNetworkTimeoutSeconds.ToString(); - } -#if !EOS_DISABLE - GUIEditorUtility.AssigningTextField("Task Network Timeout Seconds", ref taskNetworkTimeoutSecondsAsString, - tooltip: $"(Optional) Define the maximum amount of time network calls will run in the EOS SDK before timing out while the {nameof(Epic.OnlineServices.Platform.NetworkStatus)} is not {nameof(Epic.OnlineServices.Platform.NetworkStatus.Online)}. Defaults to 30 seconds if not set or less than or equal to zero."); -#endif - if (taskNetworkTimeoutSecondsAsString.Length != 0) - { - try + if (Activator.CreateInstance(constructedType) is not IPlatformConfigEditor editor) { - mainEOSConfigFile.taskNetworkTimeoutSeconds = Convert.ToDouble(taskNetworkTimeoutSecondsAsString); + Debug.LogError($"Could not load config editor for platform \"{platform}\"."); + continue; } - catch + +#if !DEBUG_SHOW_UNAVAILABLE_PLATFORMS + // Do not add the platform if it is not currently available. + if (!editor.IsPlatformAvailable()) { - Debug.LogWarning($"{nameof(EOSSettingsWindow)} ({nameof(OnDefaultGUI)}): {nameof(mainEOSConfigFile.taskNetworkTimeoutSeconds)} must be convertable to int, but string could not be parsed. The provided string is \"{taskNetworkTimeoutSecondsAsString}\". This value is ignored."); + // We only increment the tab index if the editor has been + // added to the tabs. + tabIndex++; + continue; } - } - else - { - mainEOSConfigFile.taskNetworkTimeoutSeconds = 0; - } +#endif - EditorGUIUtility.labelWidth = originalLabelWidth; + _platformConfigEditors.Add(editor); + + tabContents.Add(new GUIContent($" {editor.GetLabelText()}", editor.GetPlatformIconTexture())); - // This will be used on Windows via the nativerender code, unless otherwise specified - EditorGUILayout.Separator(); - GUILayout.Label("Default Client Credentials", EditorStyles.boldLabel); - GUIEditorUtility.AssigningTextField("Client ID", ref mainEOSConfigFile.clientID, - tooltip: "Client ID defined in the EOS Development Portal"); - GUIEditorUtility.AssigningTextField("Client Secret", ref mainEOSConfigFile.clientSecret, - tooltip: "Client Secret defined in the EOS Development Portal"); - GUI.SetNextControlName("KeyText"); - GUIEditorUtility.AssigningTextField("Encryption Key", ref mainEOSConfigFile.encryptionKey, - tooltip: "Used to decode files previously encoded and stored in EOS"); - GUI.SetNextControlName("GenerateButton"); - if (GUILayout.Button("Generate")) - { - //generate random 32-byte hex sequence - var rng = new Random(SystemInfo.deviceUniqueIdentifier.GetHashCode() * - (int)(EditorApplication.timeSinceStartup * 1000)); - var keyBytes = new byte[32]; - rng.NextBytes(keyBytes); - mainEOSConfigFile.encryptionKey = BitConverter.ToString(keyBytes).Replace("-", ""); - //unfocus key input field so the new key is shown - if (GUI.GetNameOfFocusedControl() == "KeyText") - { - GUI.FocusControl("GenerateButton"); - } } - if (!mainEOSConfigFile.IsEncryptionKeyValid()) + // If (for some reason) a default platform was not selected, then + // default to the first tab being selected + if (_selectedTab == -1) { - int keyLength = mainEOSConfigFile.encryptionKey?.Length ?? 0; - EditorGUILayout.HelpBox( - "Used for Player Data Storage and Title Storage. Must be left blank if unused. Encryption key must be 64 hex characters (0-9,A-F). Current length is " + - keyLength + ".", MessageType.Warning); + _selectedTab = 0; } - GUIEditorUtility.AssigningFlagTextField("Platform Flags (Seperated by '|')", - ref mainEOSConfigFile.platformOptionsFlags, 190, - "Flags used to initialize EOS Platform. Available flags are defined in PlatformFlags.cs"); - GUIEditorUtility.AssigningFlagTextField("Auth Scope Flags (Seperated by '|')", - ref mainEOSConfigFile.authScopeOptionsFlags, 210, - "Flags used to specify Auth Scope during login. Available flags are defined in AuthScopeFlags.cs"); - - GUIEditorUtility.AssigningBoolField("Always send Input to Overlay", - ref mainEOSConfigFile.alwaysSendInputToOverlay, 190, - "If true, the plugin will always send input to the overlay from the C# side to native, and handle showing the overlay. This doesn't always mean input makes it to the EOS SDK."); - -#if !EOS_DISABLE - InputStateButtonFlags toggleFriendsButtonCombinationEnum = mainEOSConfigFile.GetToggleFriendsButtonCombinationFlags(); - GUIEditorUtility.AssigningEnumField("Default Activate Overlay Button", - ref toggleFriendsButtonCombinationEnum, 190, - "Users can press the button(s) associated with this value to activate the Epic Social Overlay. Not all combinations are valid; the SDK will log an error at the start of runtime if an invalid combination is selected."); - mainEOSConfigFile.toggleFriendsButtonCombination = EnumUtility.GetEnumerator(toggleFriendsButtonCombinationEnum) - .Select(enumValue => enumValue.ToString()) - .ToList(); -#endif + _platformTabs = tabContents.ToArray(); } protected override void RenderWindow() { - int xCount = (int)(EditorGUIUtility.currentViewWidth / 200); - toolbarInt = GUILayout.SelectionGrid(toolbarInt, toolbarTitleStrings, xCount); - switch (toolbarInt) + if (_selectedTab < 0) { - case 0: - OnDefaultGUI(); - break; - default: - if (platformSpecificConfigEditors.Count > toolbarInt - 1) - { - platformSpecificConfigEditors[toolbarInt - 1].RenderAsync(); - } - - break; + _selectedTab = 0; } -#if ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE - if (GUILayout.Button("Pick Path For Generated C File")) + // Render the generic product configuration stuff. + _ = _productConfigEditor.RenderAsync(); + + if (_platformTabs != null && _platformConfigEditors.Count != 0) { - eosGeneratedCFilePath = EditorUtility.OpenFolderPanel("Pick Path For Generated C File", "", ""); + _selectedTab = GUILayout.Toolbar(_selectedTab, _platformTabs, TAB_STYLE); + GUILayout.Space(30); + + _ = _platformConfigEditors[_selectedTab].RenderAsync(); } -#endif - EditorGUILayout.Separator(); - GUILayout.Label("Config Format Options", EditorStyles.boldLabel); - GUIEditorUtility.AssigningBoolField("Save JSON in 'Pretty' Format", ref prettyPrint, 190); + + GUI.SetNextControlName("Save"); if (GUILayout.Button("Save All Changes")) { - Task.Run(() => Save(prettyPrint)); + GUI.FocusControl("Save"); + Save(); } + } - if (GUILayout.Button("Show in Explorer")) + private async void Save() + { + // Save the product config editor + await _productConfigEditor.Save(); + + // Save each of the platform config editors. + foreach (IConfigEditor editor in _platformConfigEditors) { - EditorUtility.RevealInFinder(ConfigDirectory); + await editor.Save(); } } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs.meta b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs.meta index 2a409affd..1b6a004bd 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs.meta +++ b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: bf93ccfa48b65594caf161002f4d3562 +guid: 43e463f37d0d1024eb3c8cff22f5f0be MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow_DEPRECATED.cs b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow_DEPRECATED.cs new file mode 100644 index 000000000..021f02a57 --- /dev/null +++ b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow_DEPRECATED.cs @@ -0,0 +1,469 @@ +/* +* Copyright (c) 2021 PlayEveryWare +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +//#define ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE + +// Uncomment the following line to show the old configuration window. +//#define SHOW_DEPRECATED_SETTINGS_WINDOW + +namespace PlayEveryWare.EpicOnlineServices.Editor.Windows +{ +#if !EOS_DISABLE + using Epic.OnlineServices.UI; +#endif + using PlayEveryWare.EpicOnlineServices.Utility; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using UnityEditor; + using UnityEngine; + using Utility; + using Config = EpicOnlineServices.Config; + using Random = System.Random; + + [Serializable] + [Obsolete] + public class EOSSettingsWindow_DEPRECATED : EOSEditorWindow + { + private List platformSpecificConfigEditors; + + private static readonly string ConfigDirectory = Path.Combine("Assets", "StreamingAssets", "EOS"); + + /// + /// Stores the current selected configuration tab. + /// "Main" is hard wired to be 0. By default "Main" is the selected tab. + /// Everything else is based on its position inside the + /// list, offset by -1. + /// For example if Android is at index 0 of the list, then when + /// toolbarInt is set to 1, the Android configuration should render. + /// + int toolbarInt { get; set; } + + string[] toolbarTitleStrings; + + EOSConfig mainEOSConfigFile; + +#if ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE + string eosGeneratedCFilePath = ""; +#endif + bool prettyPrint; + + public EOSSettingsWindow_DEPRECATED() : base("EOS Configuration") + { + } + + // This compile conditional is here so that the old settings window + // is not available by default, but can be turned on if the scripting + // define exists (optionally by uncommenting the define at the top of + // this file). +#if SHOW_DEPRECATED_SETTINGS_WINDOW + [MenuItem("EOS Plugin/EOS Configuration [DEPRECATED]", priority = 1)] + public static void ShowWindow() + { + var window = GetWindow(); + window.SetIsEmbedded(false); + } +#endif + + [SettingsProvider] + public static SettingsProvider CreateProjectSettingsProvider() + { + var settingsWindow = CreateInstance(); + string[] keywords = {"Epic", "EOS", "Online", "Services", "PlayEveryWare"}; + // mark the editor window as being embedded, so it skips auto formatting stuff. + settingsWindow.SetIsEmbedded(true); + var provider = new SettingsProvider($"Preferences/{settingsWindow.WindowTitle}", SettingsScope.Project) + { + label = settingsWindow.WindowTitle, + keywords = keywords, + guiHandler = searchContext => + { + settingsWindow.OnGUI(); + } + }; + + return provider; + } + + private string GenerateEOSGeneratedFile(EOSConfig aEOSConfig) + { + return string.Format( + String.Join("\n", "#define EOS_PRODUCT_NAME \"{0}\"", "#define EOS_PRODUCT_VERSION \"{1}\"", + "#define EOS_SANDBOX_ID \"{2}\"", "#define EOS_PRODUCT_ID \"{3}\"", + "#define EOS_DEPLOYMENT_ID \"{4}\"", "#define EOS_CLIENT_SECRET \"{5}\"", + "#define EOS_CLIENT_ID \"{6}\""), aEOSConfig.productName, + aEOSConfig.productVersion, + aEOSConfig.productID, + aEOSConfig.sandboxID, + aEOSConfig.deploymentID, + aEOSConfig.clientSecret, + aEOSConfig.clientID) + + @" +_WIN32 || _WIN64 +#define PLATFORM_WINDOWS 1 +#endif + +#if _WIN64 +#define PLATFORM_64BITS 1 +#else +#define PLATFORM_32BITS 1 +#endif + + extern ""C"" __declspec(dllexport) char* __stdcall GetConfigAsJSONString() +{ + return ""{"" + ""productName:"" EOS_PRODUCT_NAME "","" + ""productVersion: "" EOS_PRODUCT_VERSION "","" + ""productID: "" EOS_PRODUCT_ID "","" + ""sandboxID: "" EOS_SANDBOX_ID "","" + ""deploymentID: "" EOS_DEPLOYMENT_ID "","" + ""clientSecret: "" EOS_CLIENT_SECRET "","" + ""clientID: "" EOS_CLIENT_ID + + ""}"" + ; + }"; + } + + protected override async Task AsyncSetup() + { + if (!Directory.Exists(ConfigDirectory)) + { + Directory.CreateDirectory(ConfigDirectory); + } + + mainEOSConfigFile = await Config.GetAsync(); + + platformSpecificConfigEditors ??= new List(); + List toolbarStrings = new(new[] { "Main" }); + foreach (PlatformManager.Platform platform in Enum.GetValues(typeof(PlatformManager.Platform))) + { + // Windows platform overrides are currently not displayed, + // because the values for Windows are read from the main + // EpicOnlineServicesConfig.json file. + if (platform == PlatformManager.Platform.Windows) + { + continue; + } + + if (!PlatformManager.TryGetConfigType(platform, out Type configType) || null == configType) + { + continue; + } + + Type constructedType = + typeof(PlatformConfigEditor<>).MakeGenericType(configType); + + if (Activator.CreateInstance(constructedType) is not IPlatformConfigEditor editor) + { + Debug.LogError($"Could not load config editor for platform \"{platform}\"."); + continue; + } + + // Do not add the platform if it is not currently available. + if (!editor.IsPlatformAvailable()) + { + continue; + } + + platformSpecificConfigEditors.Add(editor); + + toolbarStrings.Add(editor.GetLabelText()); + } + + toolbarTitleStrings = toolbarStrings.ToArray(); + + await base.AsyncSetup(); + } + + private async Task Save(bool usePrettyFormat) + { + await mainEOSConfigFile.WriteAsync(usePrettyFormat); + + foreach (var platformSpecificConfigEditor in platformSpecificConfigEditors) + { + await platformSpecificConfigEditor.Save(usePrettyFormat); + } + +#if ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE + string generatedCFile = GenerateEOSGeneratedFile(mainEOSConfigFile.Data); + File.WriteAllText(Path.Combine(eosGeneratedCFilePath, "EOSGenerated.c"), generatedCFile); +#endif + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + private void OnDefaultGUI() + { + GUILayout.Label("Epic Online Services", EditorStyles.boldLabel); + + float originalLabelWidth = EditorGUIUtility.labelWidth; + + EditorGUIUtility.labelWidth = 200; + + // TODO: Id the Product Name userfacing? If so, we need loc + GUIEditorUtility.AssigningTextField("Product Name", ref mainEOSConfigFile.productName, + tooltip: "Product Name defined in the EOS Development Portal"); + + // TODO: bool to take product version form application version; should be automatic? + GUIEditorUtility.AssigningTextField("Product Version", ref mainEOSConfigFile.productVersion, + tooltip: "Version of Product"); + GUIEditorUtility.AssigningTextField("Product ID", ref mainEOSConfigFile.productID, + tooltip: "Product ID defined in the EOS Development Portal"); + GUIEditorUtility.AssigningTextField("Sandbox ID", ref mainEOSConfigFile.sandboxID, + tooltip: "Sandbox ID defined in the EOS Development Portal"); + GUIEditorUtility.AssigningTextField("Deployment ID", ref mainEOSConfigFile.deploymentID, + tooltip: "Deployment ID defined in the EOS Development Portal"); + + GUIEditorUtility.AssigningBoolField("Is Server", ref mainEOSConfigFile.isServer, + tooltip: "Set to 'true' if the application is a dedicated game serve"); + + EditorGUILayout.LabelField("Sandbox Deployment Overrides"); + if (mainEOSConfigFile.sandboxDeploymentOverrides == null) + { + mainEOSConfigFile.sandboxDeploymentOverrides = new List(); + } + + for (int i = 0; i < mainEOSConfigFile.sandboxDeploymentOverrides.Count; ++i) + { + EditorGUILayout.BeginHorizontal(); + GUIEditorUtility.AssigningTextField("Sandbox ID", + ref mainEOSConfigFile.sandboxDeploymentOverrides[i].sandboxID, + tooltip: "Deployment ID will be overridden when Sandbox ID is set to this", labelWidth: 70); + mainEOSConfigFile.sandboxDeploymentOverrides[i].sandboxID = + mainEOSConfigFile.sandboxDeploymentOverrides[i].sandboxID.Trim(); + GUIEditorUtility.AssigningTextField("Deployment ID", + ref mainEOSConfigFile.sandboxDeploymentOverrides[i].deploymentID, + tooltip: "Deployment ID to use for override", labelWidth: 90); + mainEOSConfigFile.sandboxDeploymentOverrides[i].deploymentID = + mainEOSConfigFile.sandboxDeploymentOverrides[i].deploymentID.Trim(); + if (GUILayout.Button("Remove", GUILayout.MaxWidth(70))) + { + mainEOSConfigFile.sandboxDeploymentOverrides.RemoveAt(i); + } + + EditorGUILayout.EndHorizontal(); + } + + if (GUILayout.Button("Add", GUILayout.MaxWidth(100))) + { + mainEOSConfigFile.sandboxDeploymentOverrides.Add(new SandboxDeploymentOverride()); + } + + GUIEditorUtility.AssigningULongToStringField("Thread Affinity: networkWork", + ref mainEOSConfigFile.ThreadAffinity_networkWork, + tooltip: "(Optional) Specifies thread affinity for network management that is not IO"); + GUIEditorUtility.AssigningULongToStringField("Thread Affinity: storageIO", + ref mainEOSConfigFile.ThreadAffinity_storageIO, + tooltip: "(Optional) Specifies affinity for threads that will interact with a storage device"); + GUIEditorUtility.AssigningULongToStringField("Thread Affinity: webSocketIO", + ref mainEOSConfigFile.ThreadAffinity_webSocketIO, + tooltip: "(Optional) Specifies affinity for threads that generate web socket IO"); + GUIEditorUtility.AssigningULongToStringField("Thread Affinity: P2PIO", + ref mainEOSConfigFile.ThreadAffinity_P2PIO, + tooltip: + "(Optional) Specifies affinity for any thread that will generate IO related to P2P traffic and management"); + GUIEditorUtility.AssigningULongToStringField("Thread Affinity: HTTPRequestIO", + ref mainEOSConfigFile.ThreadAffinity_HTTPRequestIO, + tooltip: "(Optional) Specifies affinity for any thread that will generate http request IO"); + GUIEditorUtility.AssigningULongToStringField("Thread Affinity: RTCIO", + ref mainEOSConfigFile.ThreadAffinity_RTCIO, + tooltip: + "(Optional) Specifies affinity for any thread that will generate IO related to RTC traffic and management"); + + string timeBudgetAsString = ""; + + if (mainEOSConfigFile.tickBudgetInMilliseconds != 0) + { + timeBudgetAsString = mainEOSConfigFile.tickBudgetInMilliseconds.ToString(); + } + + GUIEditorUtility.AssigningTextField("Time Budget in milliseconds", ref timeBudgetAsString, + tooltip: "(Optional) Define the maximum amount of execution time the EOS SDK can use each frame"); + + if (timeBudgetAsString.Length != 0) + { + try + { + mainEOSConfigFile.tickBudgetInMilliseconds = Convert.ToUInt32(timeBudgetAsString, 10); + } + catch + { + Debug.LogWarning($"{nameof(EOSSettingsWindow_DEPRECATED)} ({nameof(OnDefaultGUI)}): {nameof(mainEOSConfigFile.tickBudgetInMilliseconds)} must be convertable to int, but string could not be parsed. The provided string is \"{timeBudgetAsString}\". This value is ignored."); + } + } + else + { + mainEOSConfigFile.tickBudgetInMilliseconds = 0; + } + + string taskNetworkTimeoutSecondsAsString = ""; + + if (mainEOSConfigFile.taskNetworkTimeoutSeconds != 0) + { + taskNetworkTimeoutSecondsAsString = mainEOSConfigFile.taskNetworkTimeoutSeconds.ToString(); + } +#if !EOS_DISABLE + GUIEditorUtility.AssigningTextField("Task Network Timeout Seconds", ref taskNetworkTimeoutSecondsAsString, + tooltip: $"(Optional) Define the maximum amount of time network calls will run in the EOS SDK before timing out while the {nameof(Epic.OnlineServices.Platform.NetworkStatus)} is not {nameof(Epic.OnlineServices.Platform.NetworkStatus.Online)}. Defaults to 30 seconds if not set or less than or equal to zero."); +#endif + if (taskNetworkTimeoutSecondsAsString.Length != 0) + { + try + { + mainEOSConfigFile.taskNetworkTimeoutSeconds = Convert.ToDouble(taskNetworkTimeoutSecondsAsString); + } + catch + { + Debug.LogWarning($"{nameof(EOSSettingsWindow_DEPRECATED)} ({nameof(OnDefaultGUI)}): {nameof(mainEOSConfigFile.taskNetworkTimeoutSeconds)} must be convertable to int, but string could not be parsed. The provided string is \"{taskNetworkTimeoutSecondsAsString}\". This value is ignored."); + } + } + else + { + mainEOSConfigFile.taskNetworkTimeoutSeconds = 0; + } + + EditorGUIUtility.labelWidth = originalLabelWidth; + + // This will be used on Windows via the nativerender code, unless otherwise specified + EditorGUILayout.Separator(); + GUILayout.Label("Default Client Credentials", EditorStyles.boldLabel); + GUIEditorUtility.AssigningTextField("Client ID", ref mainEOSConfigFile.clientID, + tooltip: "Client ID defined in the EOS Development Portal"); + GUIEditorUtility.AssigningTextField("Client Secret", ref mainEOSConfigFile.clientSecret, + tooltip: "Client Secret defined in the EOS Development Portal"); + GUI.SetNextControlName("KeyText"); + GUIEditorUtility.AssigningTextField("Encryption Key", ref mainEOSConfigFile.encryptionKey, + tooltip: "Used to decode files previously encoded and stored in EOS"); + GUI.SetNextControlName("GenerateButton"); + if (GUILayout.Button("Generate")) + { + //generate random 32-byte hex sequence + var rng = new Random(SystemInfo.deviceUniqueIdentifier.GetHashCode() * + (int)(EditorApplication.timeSinceStartup * 1000)); + var keyBytes = new byte[32]; + rng.NextBytes(keyBytes); + mainEOSConfigFile.encryptionKey = BitConverter.ToString(keyBytes).Replace("-", ""); + //unfocus key input field so the new key is shown + if (GUI.GetNameOfFocusedControl() == "KeyText") + { + GUI.FocusControl("GenerateButton"); + } + } + +#if !EOS_DISABLE + if (!EOSClientCredentials.IsEncryptionKeyValid(mainEOSConfigFile.encryptionKey)) + { + int keyLength = mainEOSConfigFile.encryptionKey?.Length ?? 0; + EditorGUILayout.HelpBox( + "Used for Player Data Storage and Title Storage. Must be left blank if unused. Encryption key must be 64 hex characters (0-9,A-F). Current length is " + + keyLength + ".", MessageType.Warning); + } + + GUIEditorUtility.AssigningEnumField( + "Platform Flags", + ref mainEOSConfigFile.platformOptionsFlags, + 190, + "Flags used to initialize EOS Platform. Available " + + "flags are defined in PlatformFlags.cs"); + + GUIEditorUtility.AssigningEnumField( + "Auth Scope Flags", + ref mainEOSConfigFile.authScopeOptionsFlags, + 190, + "Flags used to specify Auth Scope during login. " + + "Available flags are defined in AuthScopeFlags.cs"); + + GUIEditorUtility.AssigningEnumField( + "Default Activate Overlay Button", + ref mainEOSConfigFile.toggleFriendsButtonCombination, + 190, + "Users can press the button(s) associated with this " + + "value to activate the Epic Social Overlay. Not all " + + "combinations are valid; the SDK will log an error at the " + + "start of runtime if an invalid combination is selected."); +#endif + + GUIEditorUtility.AssigningBoolField("Always send Input to Overlay", + ref mainEOSConfigFile.alwaysSendInputToOverlay, 190, + "If true, the plugin will always send input to the overlay from the C# side to native, and handle showing the overlay. This doesn't always mean input makes it to the EOS SDK."); + + } + + protected override void RenderWindow() + { + int xCount = (int)(EditorGUIUtility.currentViewWidth / 200); + + // Determine the new toolbarInt state, so that it can be compared + // against the current value, determining if this changed + int newToolbarInt = GUILayout.SelectionGrid(toolbarInt, toolbarTitleStrings, xCount); + + // If the selection is now different, deselect all selected textboxes + // This is to address #EOS-2085: Fix Editor Phantom Fields, + // wherein selecting a text box, then navigating to another config + // tab, would result in a "phantom" value appearing + if (newToolbarInt != toolbarInt && EditorGUIUtility.keyboardControl > 0) + { + GUI.FocusControl(null); + } + + toolbarInt = newToolbarInt; + + switch (toolbarInt) + { + case 0: + OnDefaultGUI(); + break; + default: + if (platformSpecificConfigEditors.Count > toolbarInt - 1) + { + platformSpecificConfigEditors[toolbarInt - 1].RenderAsync(); + } + + break; + } + +#if ALLOW_CREATION_OF_EOS_CONFIG_AS_C_FILE + if (GUILayout.Button("Pick Path For Generated C File")) + { + eosGeneratedCFilePath = EditorUtility.OpenFolderPanel("Pick Path For Generated C File", "", ""); + } +#endif + EditorGUILayout.Separator(); + GUILayout.Label("Config Format Options", EditorStyles.boldLabel); + GUIEditorUtility.AssigningBoolField("Save JSON in 'Pretty' Format", ref prettyPrint, 190); + if (GUILayout.Button("Save All Changes")) + { + Task.Run(() => Save(prettyPrint)); + } + + if (GUILayout.Button("Show in Explorer")) + { + EditorUtility.RevealInFinder(ConfigDirectory); + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Platforms/iOS/IOSConfigEditor.cs.meta b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow_DEPRECATED.cs.meta similarity index 83% rename from Assets/Plugins/Source/Editor/Platforms/iOS/IOSConfigEditor.cs.meta rename to Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow_DEPRECATED.cs.meta index b845c2b64..2a409affd 100644 --- a/Assets/Plugins/Source/Editor/Platforms/iOS/IOSConfigEditor.cs.meta +++ b/Assets/Plugins/Source/Editor/EditorWindows/EOSSettingsWindow_DEPRECATED.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b60dbeaa4bae3b444b1ffaffa25f9d74 +guid: bf93ccfa48b65594caf161002f4d3562 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Plugins/Source/Editor/EditorWindows/EOSUnitTestSettingsWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/EOSUnitTestSettingsWindow.cs index df137eb8b..e7378acb0 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/EOSUnitTestSettingsWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/EOSUnitTestSettingsWindow.cs @@ -20,6 +20,8 @@ * SOFTWARE. */ +#if !EOS_DISABLE + namespace PlayEveryWare.EpicOnlineServices.Tests.Editor { using EpicOnlineServices.Editor; @@ -37,7 +39,7 @@ public class EOSUnitTestSettingsWindow : EOSEditorWindow public EOSUnitTestSettingsWindow() : base("EOS Unit Test Configuration") { } - [MenuItem("Tools/EOS Plugin/Advanced/Unit Test Configuration")] + [MenuItem("EOS Plugin/Advanced/Unit Test Configuration")] public static void OpenUnitTestSettingsWindow() { GetWindow(); @@ -60,3 +62,4 @@ protected async override void RenderWindow() } } } +#endif \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/EditorWindows/InstallEOSZipWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/InstallEOSZipWindow.cs index afa45e6d6..13d06f69f 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/InstallEOSZipWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/InstallEOSZipWindow.cs @@ -66,7 +66,7 @@ private class PlatformImportInfoList private string pathToImportDescDirectory; private PlatformImportInfoList importInfoList; - [MenuItem("Tools/EOS Plugin/Install EOS zip")] + [MenuItem("EOS Plugin/Advanced/Install EOS zip")] public static void ShowWindow() { GetWindow(); diff --git a/Assets/Plugins/Source/Editor/EditorWindows/LogLevelWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/LogLevelWindow.cs index 37ad0b31b..525bfce4c 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/LogLevelWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/LogLevelWindow.cs @@ -45,7 +45,7 @@ public LogLevelWindow() : base("Log Level Configuration") { } - [MenuItem("Tools/EOS Plugin/Log Level Configuration")] + [MenuItem("EOS Plugin/Log Level Configuration", priority = 3)] public static void ShowWindow() { GetWindow(); diff --git a/Assets/Plugins/Source/Editor/EditorWindows/PluginVersionWindow.cs b/Assets/Plugins/Source/Editor/EditorWindows/PluginVersionWindow.cs index a0171048e..dfd478231 100644 --- a/Assets/Plugins/Source/Editor/EditorWindows/PluginVersionWindow.cs +++ b/Assets/Plugins/Source/Editor/EditorWindows/PluginVersionWindow.cs @@ -54,7 +54,7 @@ private class UPMPackage public string version; } - [MenuItem("Tools/EOS Plugin/Version", false, 100)] + [MenuItem("EOS Plugin/Version", false, 4)] public static void ShowWindow() { GetWindow(); diff --git a/Assets/Plugins/Source/Editor/Platforms/Android/AndroidBuilder.cs b/Assets/Plugins/Source/Editor/Platforms/Android/AndroidBuilder.cs index f87030fe9..b540cd9d2 100644 --- a/Assets/Plugins/Source/Editor/Platforms/Android/AndroidBuilder.cs +++ b/Assets/Plugins/Source/Editor/Platforms/Android/AndroidBuilder.cs @@ -20,6 +20,8 @@ * SOFTWARE. */ +#if !EOS_DISABLE + namespace PlayEveryWare.EpicOnlineServices.Editor.Build { using Config; @@ -344,7 +346,7 @@ private static void ConfigureGradleTemplateProperties() private static void ConfigureEOSDependentLibrary() { - string clientIDAsLower = Config.Get().clientID.ToLower(); + string clientIDAsLower = PlatformManager.GetPlatformConfig().clientCredentials.ClientId.ToLower(); var pathToEOSValuesConfig = GetAndroidEOSValuesConfigPath(); var currentEOSValuesConfigAsXML = new System.Xml.XmlDocument(); @@ -398,4 +400,6 @@ private static void CopyFromSourceToPluginFolder_Android(string sourcePath, stri Path.Combine(destPath, filename), true); } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Platforms/Windows/WindowsBuilder.cs b/Assets/Plugins/Source/Editor/Platforms/Windows/WindowsBuilder.cs index 8bc9be356..a8cbfefc3 100644 --- a/Assets/Plugins/Source/Editor/Platforms/Windows/WindowsBuilder.cs +++ b/Assets/Plugins/Source/Editor/Platforms/Windows/WindowsBuilder.cs @@ -24,6 +24,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Build { #if !EOS_DISABLE using Epic.OnlineServices.Platform; + using Extensions; #endif using Config; using Config = EpicOnlineServices.Config; @@ -83,17 +84,21 @@ public override void PostBuild(BuildReport report) private static async void ConfigureAndInstallBootstrapper(BuildReport report) { -#if !EOS_DISABLE +#if EOS_DISABLE + // If EOS_DISABLE is defined, then the bootstrapper should never be included + await System.Threading.Tasks.Task.CompletedTask; + return; +#else // Determine if 'DisableOverlay' is set in Platform Flags. If it is, then the EOSBootstrapper.exe is not included in the build, // because without needing the overlay, the EOSBootstrapper.exe is not useful to users of the plugin - EOSConfig configuration = await Config.GetAsync(); - PlatformFlags configuredFlags = configuration.GetPlatformFlags(); + PlatformConfig configuration = PlatformManager.GetPlatformConfig(); + PlatformFlags configuredFlags = configuration.platformOptionsFlags.Unwrap(); if (configuredFlags.HasFlag(PlatformFlags.DisableOverlay)) { Debug.Log($"The '{nameof(PlatformFlags.DisableOverlay)}' flag has been configured, EOSBootstrapper.exe will not be included in this build."); return; } -#endif + /* * NOTE: * @@ -123,7 +128,7 @@ private static async void ConfigureAndInstallBootstrapper(BuildReport report) */ // Determine whether to install EAC - + ToolsConfig toolsConfig = await Config.GetAsync(); string bootstrapperName = null; @@ -150,6 +155,7 @@ private static async void ConfigureAndInstallBootstrapper(BuildReport report) InstallBootStrapper(bootstrapperTarget, installDirectory, pathToEOSBootStrapperTool, bootstrapperName); +#endif } private static void InstallBootStrapper(string appFilenameExe, string installDirectory, diff --git a/Assets/Plugins/Source/Editor/Platforms/iOS/IOSBuilder.cs b/Assets/Plugins/Source/Editor/Platforms/iOS/IOSBuilder.cs index 569a640b4..615e61452 100644 --- a/Assets/Plugins/Source/Editor/Platforms/iOS/IOSBuilder.cs +++ b/Assets/Plugins/Source/Editor/Platforms/iOS/IOSBuilder.cs @@ -26,7 +26,6 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Build using System.IO; using UnityEditor; using UnityEditor.Build.Reporting; - using Utility; // This conditional is here so that no compiler errors will happen if the // Unity Editor is not configured to build for iOS. diff --git a/Assets/Plugins/Source/Editor/Utility/CreateXMLLinkUtility.cs b/Assets/Plugins/Source/Editor/Utility/CreateXMLLinkUtility.cs index 4aaff04c5..cd69fee9c 100644 --- a/Assets/Plugins/Source/Editor/Utility/CreateXMLLinkUtility.cs +++ b/Assets/Plugins/Source/Editor/Utility/CreateXMLLinkUtility.cs @@ -28,7 +28,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Utility { public static class CreateXMLLinkUtility { - [MenuItem("Tools/EOS Plugin/Create link.xml")] + [MenuItem("EOS Plugin/Advanced/Create link.xml")] static void CreateLinkXml() { var linkSourceFilePath = Path.Combine("Packages", EOSPackageInfo.PackageName, "Editor", "link.xml"); @@ -55,7 +55,7 @@ static void CreateLinkXml() } //disable menu item if not running within a UPM package - [MenuItem("Tools/EOS Plugin/Create link.xml", true)] + [MenuItem("EOS Plugin/Advanced/Create link.xml", true)] static bool ValidateCreateLinkXml() { return Directory.Exists(Path.Combine("Packages", EOSPackageInfo.PackageName, "Editor")); diff --git a/Assets/Plugins/Source/Editor/Utility/EACUtility.cs b/Assets/Plugins/Source/Editor/Utility/EACUtility.cs index 23a6484cb..42f74a7b8 100644 --- a/Assets/Plugins/Source/Editor/Utility/EACUtility.cs +++ b/Assets/Plugins/Source/Editor/Utility/EACUtility.cs @@ -22,6 +22,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Build { + using Common.Extensions; using UnityEditor.Build.Reporting; using UnityEditor; using UnityEngine; @@ -365,17 +366,18 @@ private static void ReplaceFileContentVars(string filepath, string buildExeName) string fileContents = reader.ReadToEnd(); reader.Close(); - EOSConfig eosConfig = Config.Get(); + ProductConfig productConfig = Config.Get(); + PlatformConfig platformConfig = PlatformManager.GetPlatformConfig(); var sb = new System.Text.StringBuilder(fileContents); sb.Replace("", Application.productName); sb.Replace("", buildExeName); sb.Replace("", Path.GetFileNameWithoutExtension(buildExeName)); - sb.Replace("", eosConfig.productName); - sb.Replace("", eosConfig.productID); - sb.Replace("", eosConfig.sandboxID); - sb.Replace("", eosConfig.deploymentID); + sb.Replace("", productConfig.ProductName); + sb.Replace("", productConfig.ProductId.ToString("N").ToLowerInvariant()); + sb.Replace("", platformConfig.deployment.SandboxId.ToString()); + sb.Replace("", platformConfig.deployment.DeploymentId.ToString("N").ToLowerInvariant()); fileContents = sb.ToString(); @@ -420,7 +422,7 @@ public static void ConfigureEAC(BuildReport report) if (!string.IsNullOrWhiteSpace(toolPath)) { - var productId = (Config.Get()).productID; + var productId = Config.Get().ProductId.ToString("N").ToLowerInvariant(); GenerateIntegrityCert(report, toolPath, productId, toolsConfig.pathToEACPrivateKey, toolsConfig.pathToEACCertificate, cfgPath); } diff --git a/Assets/Plugins/Source/Editor/Utility/GUIEditorUtility.cs b/Assets/Plugins/Source/Editor/Utility/GUIEditorUtility.cs index c9055ce5b..3e53c77a6 100644 --- a/Assets/Plugins/Source/Editor/Utility/GUIEditorUtility.cs +++ b/Assets/Plugins/Source/Editor/Utility/GUIEditorUtility.cs @@ -22,17 +22,42 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Utility { + using Common; + + + using Epic.OnlineServices.Platform; + using EpicOnlineServices.Utility; using System; using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; using UnityEditor; + using UnityEditorInternal; using UnityEngine; + using Config = EpicOnlineServices.Config; public static class GUIEditorUtility { - private const float MaximumButtonWidth = 100f; + /// + /// Maximum width allowed for a button. + /// + private const float MAXIMUM_BUTTON_WIDTH = 100f; + + /// + /// Style utilized for hint label overlays. + /// + private static readonly GUIStyle HINT_STYLE = new(GUI.skin.label) + { + normal = new GUIStyleState() { textColor = Color.gray }, + fontStyle = FontStyle.Italic + }; - private static GUIContent CreateGUIContent(string label, string tooltip = null) + private const int HINT_RECT_ADJUST_X = 2; + private const int HINT_RECT_ADJUST_Y = 1; + + private static GUIContent CreateGUIContent(string label, string tooltip = null, bool bold = false) { label ??= ""; return tooltip == null ? new GUIContent(label) : new GUIContent(label, tooltip); @@ -45,26 +70,21 @@ private static GUIContent CreateGUIContent(string label, string tooltip = null) /// Text to display when the foldout is shown. /// Text to display when the foldout is closed. /// Function to call when foldout is open. - public static void RenderFoldout(ref bool isOpen, string hideLabel, string showLabel, Action renderContents) + public static bool RenderFoldout(bool isOpen, string hideLabel, string showLabel, Action renderContents, string tooltip = null) { - isOpen = EditorGUILayout.Foldout(isOpen, (isOpen) ? hideLabel : showLabel); + isOpen = EditorGUILayout.Foldout(isOpen, isOpen ? hideLabel : showLabel); if (!isOpen) { - return; + return false; } - // This simulates the foldout being indented - GUILayout.BeginVertical(); - GUILayout.BeginHorizontal(); - GUILayout.Space(20f); renderContents(); - GUILayout.EndHorizontal(); - GUILayout.Space(10f); - GUILayout.EndVertical(); + return true; } - public static void AssigningFlagTextField(string label, ref List flags, float labelWidth = -1, string tooltip = null) + public static void AssigningTextField(string label, ref string value, float labelWidth = -1, + string tooltip = null) { float originalLabelWidth = EditorGUIUtility.labelWidth; if (labelWidth >= 0) @@ -72,14 +92,18 @@ public static void AssigningFlagTextField(string label, ref List flags, EditorGUIUtility.labelWidth = labelWidth; } - var collectedEOSplatformFlags = String.Join("|", flags ?? new List()); - var platformFlags = EditorGUILayout.TextField(CreateGUIContent(label, tooltip), collectedEOSplatformFlags); - flags = new List(platformFlags.Split('|')); + var newValue = EditorGUILayout.TextField(CreateGUIContent(label, tooltip), value ?? "", + GUILayout.ExpandWidth(true)); + if (newValue != null) + { + value = newValue; + } EditorGUIUtility.labelWidth = originalLabelWidth; } - public static void AssigningTextField(string label, ref string value, float labelWidth = -1, string tooltip = null) + public static void AssigningULongToStringField(string label, ref ulong? value, float labelWidth = -1, + string tooltip = null) { float originalLabelWidth = EditorGUIUtility.labelWidth; if (labelWidth >= 0) @@ -87,16 +111,45 @@ public static void AssigningTextField(string label, ref string value, float labe EditorGUIUtility.labelWidth = labelWidth; } - var newValue = EditorGUILayout.TextField(CreateGUIContent(label, tooltip), value ?? "", GUILayout.ExpandWidth(true)); - if (newValue != null) + EditorGUILayout.BeginHorizontal(); + + var guiLabel = CreateGUIContent(label, tooltip); + string textToDisplay = string.Empty; + + if (value.HasValue) { - value = newValue; + textToDisplay = value.Value.ToString(); } + string newTextValue = EditorGUILayout.TextField(guiLabel, textToDisplay, GUILayout.ExpandWidth(true)); + + if (GUILayout.Button("Clear", GUILayout.MaxWidth(50))) + { + value = null; + + // Remove focus from the control so it doesn't display "phantom" + // values + GUI.FocusControl(null); + } + else + { + if (string.IsNullOrEmpty(newTextValue)) + { + value = null; + } + else if (ulong.TryParse(newTextValue, out ulong newLongValue)) + { + value = newLongValue; + } + } + + EditorGUILayout.EndHorizontal(); + EditorGUIUtility.labelWidth = originalLabelWidth; } - public static void AssigningULongToStringField(string label, ref string value, float labelWidth = -1, string tooltip = null) + public static void AssigningBoolField(string label, ref bool value, float labelWidth = -1, + string tooltip = null) { float originalLabelWidth = EditorGUIUtility.labelWidth; if (labelWidth >= 0) @@ -104,43 +157,60 @@ public static void AssigningULongToStringField(string label, ref string value, f EditorGUIUtility.labelWidth = labelWidth; } - try - { - EditorGUILayout.BeginHorizontal(); - var newValueAsString = EditorGUILayout.TextField(CreateGUIContent(label, tooltip), value == null ? "" : value, GUILayout.ExpandWidth(true)); + var newValue = EditorGUILayout.Toggle(CreateGUIContent(label, tooltip), value, GUILayout.ExpandWidth(true)); + value = newValue; - if (GUILayout.Button("Clear", GUILayout.MaxWidth(50))) - { - value = null; - } - else - { - if (string.IsNullOrWhiteSpace(newValueAsString)) - { - value = null; - return; - } + EditorGUIUtility.labelWidth = originalLabelWidth; + } - var valueAsLong = ulong.Parse(newValueAsString); - value = valueAsLong.ToString(); - } + public static void AssigningFloatToStringField(string label, ref float? value, float labelWidth = -1, + string tooltip = null) + { + float originalLabelWidth = EditorGUIUtility.labelWidth; + if (labelWidth >= 0) + { + EditorGUIUtility.labelWidth = labelWidth; } - catch (FormatException) + + EditorGUILayout.BeginHorizontal(); + var guiLabel = CreateGUIContent(label, tooltip); + string textToDisplay = string.Empty; + + if (value.HasValue) { - value = null; + textToDisplay = value.Value.ToString(CultureInfo.InvariantCulture); } - catch (OverflowException) + + string newTextValue = EditorGUILayout.TextField(guiLabel, textToDisplay, GUILayout.ExpandWidth(true)); + + if (GUILayout.Button("Clear", GUILayout.MaxWidth(50))) { + value = null; + + // Remove focus from the control so it doesn't display "phantom" + // values + GUI.FocusControl(null); } - finally + else { - EditorGUILayout.EndHorizontal(); + if (string.IsNullOrEmpty(newTextValue)) + { + value = null; + } + else if (float.TryParse(newTextValue, out float newFloatValue)) + { + value = newFloatValue; + } } + GUILayout.EndHorizontal(); + + EditorGUIUtility.labelWidth = originalLabelWidth; } - - public static void AssigningBoolField(string label, ref bool value, float labelWidth = -1, string tooltip = null) + + public static void AssigningEnumField(string label, ref T value, float labelWidth = -1, + string tooltip = null) where T : Enum { float originalLabelWidth = EditorGUIUtility.labelWidth; if (labelWidth >= 0) @@ -148,76 +218,992 @@ public static void AssigningBoolField(string label, ref bool value, float labelW EditorGUIUtility.labelWidth = labelWidth; } - var newValue = EditorGUILayout.Toggle(CreateGUIContent(label, tooltip), value, GUILayout.ExpandWidth(true)); + var newValue = + (T)EditorGUILayout.EnumFlagsField(CreateGUIContent(label, tooltip), value, GUILayout.ExpandWidth(true)); value = newValue; EditorGUIUtility.labelWidth = originalLabelWidth; } - public static void AssigningFloatToStringField(string label, ref string value, float labelWidth = -1, string tooltip = null) + #region New methods for rendering input fields + + /// + /// Use reflection to retrieve a collection of fields that have been + /// assigned custom ConfigFieldAttribute attributes, grouping by group, + /// and sorting by group. + /// + /// A collection of config fields. + private static IOrderedEnumerable> GetFieldsByGroup() + { + return typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(field => field.GetCustomAttribute() != null) + .Select(info => (info, info.GetCustomAttribute())) + .GroupBy(r => r.Item2.Group) + .OrderBy(group => group.Key); + } + + private static IOrderedEnumerable> GetMembersByGroup() { - float originalLabelWidth = EditorGUIUtility.labelWidth; - if (labelWidth >= 0) + var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance); + var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); + + var members = fields.Cast().Concat(properties.Cast()); + + return members + .Where(member => member.GetCustomAttribute() != null) + .Select(member => (MemberInfo: member, FieldDetails: member.GetCustomAttribute())) + .GroupBy(r => r.FieldDetails.Group) + .OrderBy(group => group.Key); + } + + delegate object RenderInputDelegate(ConfigFieldAttribute attribute, object value, float labelWidth); + + static readonly Dictionary RenderInputMethods = new() + { + { typeof(Deployment), (attr, val, width) => RenderInput(attr, (Deployment)val, width) }, + { typeof(string), (attr, val, width) => RenderInput(attr, (string)val, width) }, + { typeof(ulong), (attr, val, width) => RenderInput(attr, (ulong)val, width) }, + { typeof(uint), (attr, val, width) => RenderInput(attr, (uint)val, width) }, + { typeof(ProductionEnvironments), (attr, val, width) => RenderInput(attr, (ProductionEnvironments)val, width) }, + { typeof(float), (attr, val, width) => RenderInput(attr, (float)val, width) }, + { typeof(double), (attr, val, width) => RenderInput(attr, (double)val, width) }, + { typeof(bool), (attr, val, width) => RenderInput(attr, (bool)val, width) }, + { typeof(Version), (attr, val, width) => RenderInput(attr, (Version)val, width) }, + { typeof(Guid), (attr, val, width) => RenderInput(attr, (Guid)val, width)}, + { typeof(List), (attr, val, width) => RenderInput(attr, (List)val, width)}, + { typeof(EOSClientCredentials), (attr, val, width) => RenderInput(attr, (EOSClientCredentials)val, width) }, + { typeof(SetOfNamed), (attr, val, width) => RenderInput(attr, (SetOfNamed)val, width) }, + { typeof(WrappedInitializeThreadAffinity), (attr, val, width) => RenderInput(attr, (WrappedInitializeThreadAffinity)val, width) }, + + // Add other specific types as needed + }; + + static readonly Dictionary FieldHandlers = new() + { + { ConfigFieldType.SetOfClientCredentials, HandleField> }, + { ConfigFieldType.ClientCredentials, HandleField }, + { ConfigFieldType.WrappedInitializeThreadAffinity, HandleField }, + { ConfigFieldType.Text, HandleField }, + { ConfigFieldType.FilePath, (target, fieldDetails, getValue, setValue, labelWidth) => + HandleField(target, (FilePathFieldAttribute)fieldDetails, getValue, setValue, labelWidth) }, + { ConfigFieldType.Flag, HandleField }, + { ConfigFieldType.DirectoryPath, (target, fieldDetails, getValue, setValue, labelWidth) => + HandleField(target, (DirectoryPathFieldAttribute)fieldDetails, getValue, setValue, labelWidth) }, + { ConfigFieldType.Ulong, HandleField }, + { ConfigFieldType.Double, HandleField }, + { ConfigFieldType.TextList, HandleField> }, + { ConfigFieldType.Uint, HandleField }, + { ConfigFieldType.Float, HandleField }, + { ConfigFieldType.ProductionEnvironments, HandleField }, + { ConfigFieldType.Version, HandleField }, + { ConfigFieldType.Deployment, HandleField }, + { ConfigFieldType.Guid, HandleField }, + { ConfigFieldType.Button, HandleButtonField }, + { ConfigFieldType.Enum, HandleEnumField }, + + // Add other field types as needed + }; + + private static Dictionary _foldoutStates = new(); + + private static T RenderInput(ConfigFieldAttribute attribute, T value, float labelWidth) + { + if (typeof(T) != typeof(WrappedInitializeThreadAffinity)) { - EditorGUIUtility.labelWidth = labelWidth; + return value; } - try + // Create a foldout label with a tooltip + GUIContent foldoutContent = new(attribute.Label, attribute.ToolTip); + + if (!_foldoutStates.ContainsKey(attribute.GetHashCode())) + { + _foldoutStates.Add(attribute.GetHashCode(), false); + } + + GUIStyle boldFoldoutStyle = new(EditorStyles.foldout) + { + fontStyle = FontStyle.Bold + }; + + if (!string.IsNullOrEmpty(attribute.HelpURL)) { EditorGUILayout.BeginHorizontal(); - var newValueAsString = EditorGUILayout.TextField(CreateGUIContent(label, tooltip), value == null ? "" : value, GUILayout.ExpandWidth(true)); + } + + _foldoutStates[attribute.GetHashCode()] = EditorGUILayout.Foldout(_foldoutStates[attribute.GetHashCode()], foldoutContent, true, boldFoldoutStyle); + + if (!string.IsNullOrEmpty(attribute.HelpURL)) + { + GUILayout.FlexibleSpace(); + RenderHelpIcon(attribute.HelpURL); + EditorGUILayout.EndHorizontal(); + } + + if (_foldoutStates[attribute.GetHashCode()]) + { + RenderInputs(ref value); + } + + return value; + } + + static void HandleField( + object target, + ConfigFieldAttribute fieldDetails, + Func getValue, + Action setValue, + float labelWidth) + { + var currentValue = getValue(target); + + object newValue; + + if (RenderInputMethods.TryGetValue(typeof(TField), out var renderMethod)) + { + newValue = renderMethod(fieldDetails, currentValue, labelWidth); + } + else + { + newValue = RenderInput(fieldDetails, (TField)currentValue, labelWidth); + } - if (GUILayout.Button("Clear", GUILayout.MaxWidth(50))) + setValue(target, newValue); + } + + static void HandleButtonField( + object target, + ConfigFieldAttribute fieldDetails, + Func getValue, + Action setValue, + float labelWidth) + { + if (GUILayout.Button(fieldDetails.Label) && getValue(target) is Action onClick) + { + onClick(); + } + } + + static void HandleEnumField( + object target, + ConfigFieldAttribute fieldDetails, + Func getValue, + Action setValue, + float labelWidth) + { + var enumValue = getValue(target); + Type enumType = enumValue.GetType(); + var method = typeof(GUIEditorUtility).GetMethod("RenderEnumInput", BindingFlags.Public | BindingFlags.Static); + var genericMethod = method.MakeGenericMethod(enumType); + var newValue = genericMethod.Invoke(null, new object[] { fieldDetails, enumValue, labelWidth }); + setValue(target, newValue); + } + + delegate void FieldHandler( + object target, + ConfigFieldAttribute fieldDetails, + Func getValue, + Action setValue, + float labelWidth); + + public static void RenderSectionHeader(string label) + { + GUILayout.Label(label.ToUpper(), EditorStyles.boldLabel); + Rect rect = EditorGUILayout.GetControlRect(false, 1); // Set the height to 1 pixel + EditorGUI.DrawRect(rect, Color.gray); + } + + /// + /// Render the config fields for the config that has been set to edit. + /// + /// + /// Thrown for types that are not yet implemented. + /// + /// + /// Thrown for types that are not yet implemented, and not accounted for + /// in the switch statement. + /// + public static void RenderInputs(ref T value) + { + string[] groupLabels = typeof(T).GetCustomAttribute()?.GroupLabels; + + bool groupSpecified = false; + foreach (var fieldGroup in GetMembersByGroup().ToList()) + { + List labelsInGroup = fieldGroup.Select(field => field.FieldDetails.Label).ToList(); + float labelWidth = MeasureLongestLabelWidth(labelsInGroup); + + // Check to see if there is at least a single value within the + // field group that should be enabled, if so then skip checking + // the others and continue. + bool sectionEnabled = false; + foreach (var member in fieldGroup) { - value = null; + if (value is not PlatformConfig platformConfig || + (member.FieldDetails.PlatformsEnabledOn & platformConfig.Platform) != 0) + { + sectionEnabled = true; + break; + } + } + + GUI.enabled = sectionEnabled; + + // If there is a label for the field group, then display it. + if (0 <= fieldGroup.Key && groupLabels?.Length > fieldGroup.Key && !string.IsNullOrEmpty(groupLabels[fieldGroup.Key])) + { + RenderSectionHeader(groupLabels[fieldGroup.Key]); + groupSpecified = true; } - else + + if (groupSpecified) + { + EditorGUILayout.BeginVertical("box"); + } + + foreach (var member in fieldGroup) { - if (string.IsNullOrWhiteSpace(newValueAsString)) + // Check if the config value should be disabled. + if (value is PlatformConfig platformConfig && + (member.FieldDetails.PlatformsEnabledOn & platformConfig.Platform) == 0) { - value = null; - return; + GUI.enabled = false; + // TODO: Consider whether it makes more sense to simply not display the input + member.FieldDetails.ToolTip = $"These options are not available on {PlatformManager.GetFullName(platformConfig.Platform)}."; } - var valueAsFloat = float.Parse(newValueAsString); - value = valueAsFloat.ToString(); + // Retrieve GetValue and SetValue functions + Func GetValueFn = null; + Action SetValueFn = null; + + if (member.MemberInfo is FieldInfo fieldInfo) + { + GetValueFn = fieldInfo.GetValue; + SetValueFn = fieldInfo.SetValue; + } + else if (member.MemberInfo is PropertyInfo propertyInfo) + { + GetValueFn = propertyInfo.GetValue; + SetValueFn = propertyInfo.SetValue; + } + else + { + continue; // Skip if MemberInfo is neither FieldInfo nor PropertyInfo + } + + // Use the handler from the dictionary + if (FieldHandlers.TryGetValue(member.FieldDetails.FieldType, out var handler)) + { + handler(value, member.FieldDetails, GetValueFn, SetValueFn, labelWidth); + } + else + { + throw new ArgumentOutOfRangeException($"Unhandled field type: {member.FieldDetails.FieldType}"); + } + + GUI.enabled = true; + } + + if (groupSpecified) + { + EditorGUILayout.EndVertical(); } + EditorGUILayout.Space(5); + groupSpecified = false; } - catch (FormatException) + } + + public static float MeasureLongestLabelWidth(List labels) + { + GUIStyle labelStyle = new(GUI.skin.label); + + string longestString = string.Empty; + foreach (string label in labels) { - value = null; + if (label.Length <= longestString.Length) + continue; + + longestString = label; } - catch (OverflowException) + + return MeasureLabelWidth(longestString); + } + + public static float MeasureLabelWidth(string label) + { + return new GUIStyle(GUI.skin.label).CalcSize(new GUIContent(label)).x; + } + + /// + /// Used to describe the function used to render a field of type T. + /// + /// + /// The type being rendered. + /// + /// + /// The current value to display in the field. + /// + /// + /// Optional parameters that describe the styling of the field to + /// render. + /// + /// + /// The value entered. + /// + private delegate T RenderFieldDelegate(T value, params GUILayoutOption[] options); + + /// + /// Used to describe the function used to render a basic field of type + /// T. + /// + /// + /// The type of input being rendered. + /// + /// + /// The area in which the field should be rendered. + /// + /// The value to put in the field. + /// The value entered in the field. + private delegate T RenderBasicFieldDelegate(Rect rect, T value); + + /// + /// Used to render a field with an overlay hint label when the field has + /// a value that is considered to be "default". + /// + /// + /// The type of input the field is expecting and returns. + /// + /// + /// The function used to render the input field. + /// + /// + /// The area in which to render the field. + /// + /// + /// The function used to determine if the value is default or not. + /// + /// + /// The current value to display in the input field. + /// + /// + /// The hint text to display over the input field if the value is + /// considered to be default. + /// + /// + /// The value entered into the input field. + /// + private static T RenderFieldWithHint( + RenderBasicFieldDelegate renderFieldFn, + Rect rect, + Func isDefaultFn, + T value, + string hintText) + { + string controlName = Guid.NewGuid().ToString(); + + GUI.SetNextControlName(controlName); + + T newValue = renderFieldFn(rect, value); + + if (isDefaultFn(newValue) && GUI.GetNameOfFocusedControl() != controlName) { + RenderHint(hintText, rect); } - finally + + return newValue; + } + + /// + /// Used to render a field with an overlay hint label when the field has + /// a value that is considered to be "default". + /// + /// + /// The type of input the field is expecting and returns. + /// + /// + /// The function used to render the input field. + /// + /// + /// The function used to determine if the value is default or not. + /// + /// + /// The current value to display in the input field. + /// + /// + /// The hint text to display over the input field if the value is + /// considered to be default. + /// + /// + /// The value entered into the input field. + /// + private static T RenderFieldWithHint( + RenderFieldDelegate renderFieldFn, + Func isDefaultFn, + T value, + string hintText) + { + // Generate a unique control name + string controlName = Guid.NewGuid().ToString(); + + // Set the name of the next field + GUI.SetNextControlName(controlName); + + // Render the field + T newValue = renderFieldFn(value, GUILayout.ExpandWidth(true)); + + // Check if the field is default, and that the control is not + // focused + if (isDefaultFn(newValue) && GUI.GetNameOfFocusedControl() != controlName) { - EditorGUILayout.EndHorizontal(); + RenderHint(hintText); } - EditorGUIUtility.labelWidth = originalLabelWidth; + return newValue; } - public static void AssigningEnumField(string label, ref T value, float labelWidth = -1, string tooltip = null) where T : Enum + /// + /// Used to render a label on top of the previously rendered control. + /// + /// + /// The text to display over the last rendered input field. + /// + private static void RenderHint(string hintText) { - float originalLabelWidth = EditorGUIUtility.labelWidth; - if (labelWidth >= 0) + // Get the rectangle of the last control rendered + Rect fieldRect = GUILayoutUtility.GetLastRect(); + fieldRect.x += HINT_RECT_ADJUST_X; + fieldRect.y += HINT_RECT_ADJUST_Y; + + EditorGUI.LabelField(fieldRect, hintText, HINT_STYLE); + } + + /// + /// Used to render a label at the specified rect. + /// + /// + /// The text to display as a hint for input. + /// + /// + /// The location in which to draw the hint. + /// + private static void RenderHint(string hintText, Rect rect) + { + EditorGUI.LabelField(rect, hintText, HINT_STYLE); + } + + /// + /// Renders the built-in help icon, and opens a browser to the indicated + /// url when clicked on. + /// + /// The area in which to render the icon. + /// The url to open when the icon is clicked on. + private static void RenderHelpIcon(Rect area, string url) + { + RenderHelpIconInternal(url, (content, style) => GUI.Button(area, content, style)); + } + + private static void RenderHelpIcon(string url) + { + RenderHelpIconInternal(url, (content, style) => GUILayout.Button(content, style)); + } + + private static void RenderHelpIconInternal(string url, Func renderButtonFn) + { + GUIContent helpIcon = EditorGUIUtility.IconContent("_Help"); + + Color hoverColor = !EditorGUIUtility.isProSkin ? new Color(0.2f, 0.2f, 0.2f) : new Color(0.8f, 0.8f, 0.8f); + + Texture2D hoverTexture = new(1, 1); + hoverTexture.SetPixel(0, 0, hoverColor); + hoverTexture.Apply(); + + GUIStyle helpButtonStyle = new(EditorStyles.label) { - EditorGUIUtility.labelWidth = labelWidth; + padding = new RectOffset(0, 0, 0, 0), + fixedWidth = EditorGUIUtility.singleLineHeight, + fixedHeight = EditorGUIUtility.singleLineHeight, + normal = { background = Texture2D.redTexture }, // No background by default + hover = { background = Texture2D.redTexture }, // Gray back + }; + + if (renderButtonFn(helpIcon, helpButtonStyle)) + { + Application.OpenURL(url); } + } - var newValue = (T)EditorGUILayout.EnumFlagsField(CreateGUIContent(label, tooltip), value, GUILayout.ExpandWidth(true)); - value = newValue; + private static void RenderSetOfNamed( + string label, + string tooltip, + string helpUrl, + SetOfNamed value, + Action, bool> renderItemFn, + Action addNewItemFn, + Action> removeItemFn, + ReorderableList.ElementHeightCallbackDelegate elementHeightCallback = null) + where T : IEquatable, new() + { + List> items = value.ToList(); - EditorGUIUtility.labelWidth = originalLabelWidth; + // If there is only one item, then only render one set of inputs + // instead of rendering the whole reorderable list + if (items.Count == 1) + { + // Render the single item with a "+" button + EditorGUILayout.BeginHorizontal(); + + Rect rect = EditorGUILayout.GetControlRect(); + rect.height = EditorGUIUtility.singleLineHeight; + renderItemFn(rect, items[0], true); + + // Render the "+" button to add a new item + if (GUILayout.Button("+", GUILayout.Width(24))) + { + addNewItemFn(); + } + + if (!string.IsNullOrEmpty(helpUrl)) + { + RenderHelpIcon(helpUrl); + } + + EditorGUILayout.EndHorizontal(); + } + // If there is more than one, then display the whole list + else if (items.Count > 1) + { + EditorGUILayout.Space(); + + ReorderableList list = new(items, typeof(Named)) + { + draggable = false, + drawHeaderCallback = (rect) => + { + EditorGUI.LabelField(new(rect.x, rect.y, rect.width - 20f, rect.height), + CreateGUIContent(label, tooltip)); + if (!string.IsNullOrEmpty(helpUrl)) + { + RenderHelpIcon(new(rect.x + rect.width - 20f, rect.y, 20f, rect.height), helpUrl); + } + }, + onAddCallback = (_) => addNewItemFn(), + drawElementCallback = (rect, index, _, _) => + { + rect.y += 2f; + rect.height = EditorGUIUtility.singleLineHeight; + + renderItemFn(rect, items[index], false); + }, + onRemoveCallback = (list) => + { + if (list.index < 0 || list.index >= items.Count) + { + return; + } + + removeItemFn(items[list.index]); + } + }; + + if (elementHeightCallback != null) + { + list.elementHeightCallback = elementHeightCallback; + } + + list.DoLayoutList(); + } + else + { + // TODO here + } } - #region New methods for rendering input fields + private static void RenderDeploymentInputs(ref ProductionEnvironments value) + { + ProductionEnvironments productionEnvironmentsCopy = value; + + RenderSetOfNamed( + "Deployments", + "Enter your deployments here as they appear in the Epic Dev Portal.", + "https://dev.epicgames.com/docs/dev-portal/product-management#deployments", + productionEnvironmentsCopy.Deployments, + (rect, item, nameAsLabel) => + { + float remainingWidth = rect.width; + float firstFieldWidth = rect.width * 0.25f - 5f; + float middleFieldWidth = rect.width * 0.50f - 5f; + float endFieldWidth = rect.width * 0.25f; - private delegate T InputRenderDelegate(string label, T value, float labelWidth, string tooltip); + if (nameAsLabel) + { + firstFieldWidth = MeasureLabelWidth("Deployment") + 5f; + Rect nameRect = new(rect.x, rect.y, firstFieldWidth, rect.height); + EditorGUI.LabelField(nameRect, item.Name); + } + else + { + Rect nameRect = new(rect.x, rect.y, firstFieldWidth - 5f, rect.height); + + string newItemName = RenderFieldWithHint( + EditorGUI.TextField, + nameRect, + string.IsNullOrEmpty, + item.Name, + "Sandbox Name"); + + if (!item.TrySetName(newItemName)) + { + Debug.LogError("COULD NOT CHANGE NAME!!!"); + } + } + + remainingWidth -= firstFieldWidth; + + float guidFieldWidth = middleFieldWidth; + if (productionEnvironmentsCopy.Sandboxes.Count <= 1) + { + guidFieldWidth = remainingWidth; + } + else + { + guidFieldWidth -= 5f; + } + + item.Value.DeploymentId = GuidField( + new Rect(rect.x + firstFieldWidth, rect.y, guidFieldWidth, rect.height), + item.Value.DeploymentId); + + // Only render the sandbox dropdown if there is more than + // one sandbox to select from. + if (productionEnvironmentsCopy.Sandboxes.Count > 1) + { + List sandboxLabelList = new(); + int labelIndex = 0; + int selectedIndex = 0; + foreach (Named sandbox in productionEnvironmentsCopy.Sandboxes) + { + sandboxLabelList.Add(sandbox.Name); + if (sandbox.Value.Equals(item.Value.SandboxId)) + { + selectedIndex = labelIndex; + } + + labelIndex++; + } + + int newSelectedIndex = EditorGUI.Popup( + new Rect(rect.x + firstFieldWidth + middleFieldWidth, rect.y, endFieldWidth, + rect.height), + selectedIndex, sandboxLabelList.ToArray()); + string newSelectedSandboxLabel = sandboxLabelList[newSelectedIndex]; + foreach (Named sandbox in productionEnvironmentsCopy.Sandboxes) + { + if (newSelectedSandboxLabel != sandbox.Name) + { + continue; + } + + item.Value.SandboxId = sandbox.Value; + break; + } + } + }, + () => productionEnvironmentsCopy.Deployments.Add(), + (item) => + { + if (!productionEnvironmentsCopy.Deployments.Remove(item)) + { + // TODO: Tell user why deployment could not be removed + // from the Production Environments. + } + }); + } + + private static void RenderSandboxInputs(ref ProductionEnvironments value) + { + ProductionEnvironments productionEnvironmentsCopy = value; + + RenderSetOfNamed( + "Sandboxes", + "Enter your sandboxes here, as they appear in the Epic Dev Portal.", + "https://dev.epicgames.com/docs/dev-portal/product-management#sandboxes", + productionEnvironmentsCopy.Sandboxes, + (rect, item, nameAsLabel) => + { + float currentX = rect.x; + float remainingWidth = rect.width; + + if (nameAsLabel) + { + // We are measuring "Deployment" here because it is longer than "Sandbox", and we want them to line up. + float labelWidth = MeasureLabelWidth("Deployment"); + Rect nameRect = new(currentX, rect.y, labelWidth + 5f, rect.height); + currentX += 5f + labelWidth; + remainingWidth -= labelWidth - 5f; + EditorGUI.LabelField(nameRect, item.Name); + } + else + { + float fieldWidth = (rect.width - 5f) / 2f; + Rect nameRect = new(currentX, rect.y, fieldWidth - 5f, rect.height); + currentX += fieldWidth + 5f; + remainingWidth -= fieldWidth - 5f; + string newItemName = RenderFieldWithHint( + EditorGUI.TextField, + nameRect, + string.IsNullOrEmpty, + item.Name, + "Sandbox Name"); + + if (!item.TrySetName(newItemName)) + { + Debug.LogError("COULD NOT CHANGE NAME!!!"); + } + } + + item.Value.Value = RenderFieldWithHint( + EditorGUI.TextField, + new Rect(currentX, rect.y, remainingWidth - 10f, rect.height), + string.IsNullOrEmpty, + item.Value.Value, + "Sandbox Id"); + }, + () => productionEnvironmentsCopy.Sandboxes.Add(), + (item) => + { + if (!productionEnvironmentsCopy.Sandboxes.Remove(item)) + { + // TODO: Tell user why the sandbox could not be removed. + } + } + ); + } + + + + private static SetOfNamed RenderInput(ConfigFieldAttribute configFieldAttribute, + SetOfNamed value, float labelWidth) + { + SetOfNamed clientCredentialsCopy = value; + + RenderSetOfNamed( + "Clients", + "Enter your client information here as it appears in the Epic Dev Portal.", + "https://dev.epicgames.com/docs/dev-portal/product-management#clients", + clientCredentialsCopy, + (rect, item, nameAsLabel) => + { + float remainingWidth = rect.width; + float firstFieldWidth = (rect.width - 5f) * 0.18f; + + if (nameAsLabel) + { + // We measure the width of the label "Deployment" because that's the label we want to align to. + firstFieldWidth = MeasureLabelWidth("Deployment"); + EditorGUI.LabelField(new Rect(rect.x, rect.y, firstFieldWidth, rect.height), item.Name); + } + else + { + string newItemName = RenderFieldWithHint( + EditorGUI.TextField, + new Rect(rect.x, rect.y, firstFieldWidth, rect.height), + string.IsNullOrEmpty, + item.Name, + "Client Name"); + + if (!item.TrySetName(newItemName)) + { + Debug.LogError("COULD NOT CHANGE NAME!!!"); + } + } - public static List RenderInputField(ConfigFieldAttribute configFieldDetails, List value, - float labelWidth, string tooltip = null) + remainingWidth -= firstFieldWidth; + + item.Value ??= new(); + + float clientFieldWidth = remainingWidth * 0.34f; + float clientIdFieldX = rect.x + firstFieldWidth + 5f; + remainingWidth -= clientFieldWidth; + + item.Value.ClientId = RenderFieldWithHint( + EditorGUI.TextField, + new Rect(clientIdFieldX, rect.y, clientFieldWidth, rect.height), + string.IsNullOrEmpty, + item.Value.ClientId, + "Client ID" + ); + + item.Value.ClientSecret = RenderFieldWithHint( + EditorGUI.TextField, + new Rect(rect.x + firstFieldWidth + 5f + clientFieldWidth + 5f, rect.y, remainingWidth - 10f, + rect.height), + string.IsNullOrEmpty, + item.Value.ClientSecret, + "Client Secret"); + }, + () => clientCredentialsCopy.Add(), + (item) => + { + if (!clientCredentialsCopy.Remove(item)) + { + // TODO: Tell user that credentials could not be removed. + Debug.LogError("Could not remove client credential"); + } + }); + + return clientCredentialsCopy; + } + + public static EOSClientCredentials RenderInput(ConfigFieldAttribute configFieldAttribute, + EOSClientCredentials value, + float labelWidth) + { + return InputRendererWithAlignedLabel(labelWidth, () => + { + List> credentials = Config.Get().Clients.ToList(); + List credentialsLabels = new(); + int selectedIndex = -1; + int currentIndex = 0; + foreach (Named cred in credentials) + { + if (cred.Value.Equals(value)) + { + selectedIndex = currentIndex; + } + + credentialsLabels.Add($"{cred.Name} : {cred.Value.ClientId}"); + + currentIndex++; + } + + int newIndex = EditorGUILayout.Popup( + CreateGUIContent(configFieldAttribute.Label, configFieldAttribute.ToolTip), + selectedIndex, + credentialsLabels.ToArray()); + + return (newIndex >= 0 && newIndex < credentials.Count) ? credentials[newIndex].Value : value; + }); + } + + public static WrappedInitializeThreadAffinity RenderInput(ConfigFieldAttribute attribute, WrappedInitializeThreadAffinity value) + { + EditorGUILayout.LabelField(CreateGUIContent(attribute.Label, attribute.ToolTip), new GUIStyle() { fontStyle = FontStyle.Bold }); + RenderInputs(ref value); + return value; + } + + private static Guid RenderInput(ConfigFieldAttribute configFieldDetails, Guid value, float labelWidth) + { + return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, + GuidField); + } + + private static Guid GuidField(Guid value, params GUILayoutOption[] options) + { + string tempStringName = EditorGUILayout.TextField(value.ToString(), options); + + return Guid.TryParse(tempStringName, out Guid newValue) ? newValue : value; + } + + private static Guid GuidField(Rect rect, Guid value) + { + string tempStringName = EditorGUI.TextField(rect, value.ToString()); + return Guid.TryParse(tempStringName, out Guid newValue) ? newValue : value; + } + + private static Guid GuidField(GUIContent label, Guid value, params GUILayoutOption[] options) + { + string tempStringName = EditorGUILayout.TextField(label, value.ToString(), options); + return Guid.TryParse(tempStringName, out Guid newValue) ? newValue : value; + } + + private static Version VersionField(GUIContent label, Version value, params GUILayoutOption[] options) + { + value ??= new(); + string tempStringVersion = EditorGUILayout.TextField(label, value.ToString(), options); + return Version.TryParse(tempStringVersion, out Version newValue) ? newValue : value; + } + + private static Version VersionField(Version value, params GUILayoutOption[] options) + { + value ??= new(); + string tempStringVersion = EditorGUILayout.TextField(value.ToString(), options); + return Version.TryParse(tempStringVersion, out Version newValue) ? newValue : value; + } + + public static Deployment RenderInput(ConfigFieldAttribute configFieldAttribute, Deployment value, float labelWidth) + { + return InputRendererWithAlignedLabel(labelWidth, () => + { + List> deployments = Config.Get().Environments.Deployments.ToList(); + List deploymentLabels = new(); + int selectedIndex = -1; + int currentIndex = 0; + foreach (Named deployment in deployments) + { + if (value.DeploymentId == deployment.Value.DeploymentId) + selectedIndex = currentIndex; + + deploymentLabels.Add($"{deployment.Name}: {deployment.Value.DeploymentId}"); + + currentIndex++; + } + + int newIndex = EditorGUILayout.Popup( + CreateGUIContent(configFieldAttribute.Label, configFieldAttribute.ToolTip), + selectedIndex, + deploymentLabels.ToArray()); + + return (newIndex >= 0 && newIndex < deployments.Count) ? deployments[newIndex].Value : value; + }); + } + + public static TEnum RenderEnumInput(ConfigFieldAttribute configFieldAttribute, TEnum value, float labelWidth) where TEnum : Enum + { + return InputRendererWrapper(configFieldAttribute.Label, configFieldAttribute.ToolTip, labelWidth, value, + EnumFlagsField, configFieldAttribute.HelpURL); + } + + private static TEnum EnumFlagsField(GUIContent label, TEnum value, params GUILayoutOption[] options) where TEnum : Enum + { + return (TEnum)EditorGUILayout.EnumFlagsField(label, value, options); + } + private static Version RenderInput(ConfigFieldAttribute configFieldAttribute, Version value, float labelWidth) + { + return RenderInput(value, configFieldAttribute.Label, configFieldAttribute.ToolTip, labelWidth); + } + + public static Version RenderInput(Version value, string label, string tooltip, float labelWidth) + { + return InputRendererWrapper(label, tooltip, labelWidth, value, VersionField); + } + + public static ProductionEnvironments RenderInput(ConfigFieldAttribute configFieldAttribute, + ProductionEnvironments value, float labelWidth) + { + value ??= new(); + + // Render the list of sandboxes + RenderSandboxInputs(ref value); + + // Check to see if there are any sandboxes - if there aren't any + // then you cannot add a deployment. + if (value.Sandboxes.Count == 0) + { + GUI.enabled = false; + } + + // Render the list of deployments + RenderDeploymentInputs(ref value); + + // Ensure that the GUI is always enabled before leaving scope + GUI.enabled = true; + + return value; + } + + public static List RenderInput(ConfigFieldAttribute configFieldDetails, List value, + float labelWidth) { float currentLabelWidth = EditorGUIUtility.labelWidth; @@ -236,12 +1222,12 @@ public static List RenderInputField(ConfigFieldAttribute configFieldDeta EditorGUILayout.BeginHorizontal(); GUILayout.Label(CreateGUIContent(listLabel, configFieldDetails.ToolTip)); GUILayout.FlexibleSpace(); - if (GUILayout.Button("Add", GUILayout.MaxWidth(MaximumButtonWidth))) + if (GUILayout.Button("Add", GUILayout.MaxWidth(MAXIMUM_BUTTON_WIDTH))) { newValue.Add(string.Empty); } EditorGUILayout.EndHorizontal(); - + for (var i = 0; i < newValue.Count; ++i) { bool itemRemoved = false; @@ -250,7 +1236,7 @@ public static List RenderInputField(ConfigFieldAttribute configFieldDeta newValue[i] = EditorGUILayout.TextField(newValue[i], GUILayout.ExpandWidth(true)); - if (GUILayout.Button("Remove", GUILayout.MaxWidth(MaximumButtonWidth))) + if (GUILayout.Button("Remove", GUILayout.MaxWidth(MAXIMUM_BUTTON_WIDTH))) { newValue.RemoveAt(i); itemRemoved = true; @@ -265,19 +1251,13 @@ public static List RenderInputField(ConfigFieldAttribute configFieldDeta return newValue; } - public static string RenderInputField(DirectoryPathFieldAttribute configFieldAttributeDetails, string value, float labelWidth, - string tooltip = null) + public static string RenderInput(DirectoryPathFieldAttribute configFieldAttributeDetails, string value, float labelWidth, string tooltip = null) { EditorGUILayout.BeginHorizontal(); - string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, - (label, s, width, tooltip) => - { - return EditorGUILayout.TextField(CreateGUIContent(configFieldAttributeDetails.Label, tooltip), value, - GUILayout.ExpandWidth(true)); - }); + string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, EditorGUILayout.TextField, configFieldAttributeDetails.HelpURL); - if (GUILayout.Button("Select", GUILayout.MaxWidth(MaximumButtonWidth))) + if (GUILayout.Button("Select", GUILayout.MaxWidth(MAXIMUM_BUTTON_WIDTH))) { string selectedPath = EditorUtility.OpenFolderPanel(configFieldAttributeDetails.Label, "", ""); @@ -292,18 +1272,13 @@ public static string RenderInputField(DirectoryPathFieldAttribute configFieldAtt return filePath; } - public static string RenderInputField(FilePathFieldAttribute configFieldAttributeDetails, string value, float labelWidth, string tooltip = null) + public static string RenderInput(FilePathFieldAttribute configFieldAttributeDetails, string value, float labelWidth, string tooltip = null) { EditorGUILayout.BeginHorizontal(); - string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, - (label, s, width, tooltip) => - { - return EditorGUILayout.TextField(CreateGUIContent(configFieldAttributeDetails.Label, tooltip), value, - GUILayout.ExpandWidth(true)); - }); + string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, EditorGUILayout.TextField, configFieldAttributeDetails.HelpURL); - if (GUILayout.Button("Select", GUILayout.MaxWidth(MaximumButtonWidth))) + if (GUILayout.Button("Select", GUILayout.MaxWidth(MAXIMUM_BUTTON_WIDTH))) { string selectedPath = EditorUtility.OpenFilePanel(configFieldAttributeDetails.Label, "", configFieldAttributeDetails.Extension); @@ -319,89 +1294,94 @@ public static string RenderInputField(FilePathFieldAttribute configFieldAttribut return filePath; } - public static double RenderInputField(ConfigFieldAttribute configFieldDetails, double value, float labelWidth, string tooltip = null) + public static double RenderInput(ConfigFieldAttribute configFieldDetails, double value, float labelWidth) { - return InputRendererWrapper(configFieldDetails.Label, value, labelWidth, tooltip, - (label, value1, width, s) => - { - return EditorGUILayout.DoubleField( - CreateGUIContent(configFieldDetails.Label, tooltip), - value, - GUILayout.ExpandWidth(true)); - }); + return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, EditorGUILayout.DoubleField, configFieldDetails.HelpURL); } - public static string RenderInputField(ConfigFieldAttribute configFieldDetails, string value, float labelWidth, - string tooltip = null) + public static float RenderInput(ConfigFieldAttribute configFieldDetails, float value, float labelWidth) { - return InputRendererWrapper(configFieldDetails.Label, value, labelWidth, tooltip, - (label, s, width, tooltip1) => - { - return EditorGUILayout.TextField(CreateGUIContent(configFieldDetails.Label, tooltip), value, - GUILayout.ExpandWidth(true)); - }); + return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, EditorGUILayout.FloatField, configFieldDetails.HelpURL); } - public static ulong RenderInputField(ConfigFieldAttribute configFieldDetails, ulong value, float labelWidth, - string tooltip = null) + public static string RenderInput(ConfigFieldAttribute configFieldDetails, string value, float labelWidth) { - return InputRendererWrapper(configFieldDetails.Label, value, labelWidth, tooltip, - (label, value1, width, s) => - { - _ = SafeTranslatorUtility.TryConvert(value, out long temp); + return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, EditorGUILayout.TextField, configFieldDetails.HelpURL); + } - long longValue = EditorGUILayout.LongField( - CreateGUIContent(configFieldDetails.Label, tooltip), - temp, - GUILayout.ExpandWidth(true)); + public static ulong RenderInput(ConfigFieldAttribute configFieldDetails, ulong value, float labelWidth) + { + _ = SafeTranslatorUtility.TryConvert(value, out long temp); - _ = SafeTranslatorUtility.TryConvert(longValue, out ulong newValue); + long longValue = InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, + temp, EditorGUILayout.LongField); - return newValue; - }); + return SafeTranslatorUtility.TryConvert(longValue, out ulong newValue) ? newValue : value; } - public static uint RenderInputField(ConfigFieldAttribute configFieldDetails, uint value, float labelWidth, - string tooltip = null) + private static ulong RenderInput(string label, string tooltip, ulong value, float labelWidth) { - return InputRendererWrapper(configFieldDetails.Label, value, labelWidth, tooltip, - (label, value1, width, s) => - { - _ = SafeTranslatorUtility.TryConvert(value1, out int temp); - - int intValue = EditorGUILayout.IntField( - CreateGUIContent(configFieldDetails.Label, tooltip), - temp, - GUILayout.ExpandWidth(true)); + _ = SafeTranslatorUtility.TryConvert(value, out long temp); - _ = SafeTranslatorUtility.TryConvert(intValue, out uint newValue); + long longValue = InputRendererWrapper(label, tooltip, labelWidth, + temp, EditorGUILayout.LongField); - return newValue; - }); + return SafeTranslatorUtility.TryConvert(longValue, out ulong newValue) ? newValue : value; } - public static bool RenderInputField(ConfigFieldAttribute configFieldDetails, bool value, float labelWidth, string tooltip = null) + public static uint RenderInput(ConfigFieldAttribute configFieldDetails, uint value, float labelWidth) { - return InputRendererWrapper(configFieldDetails.Label, value, labelWidth, tooltip, (s, b, arg3, arg4) => - { - return EditorGUILayout.Toggle(CreateGUIContent(configFieldDetails.Label, tooltip), value, GUILayout.ExpandWidth(true)); - }); + _ = SafeTranslatorUtility.TryConvert(value, out int temp); + + int intValue = InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, temp, + EditorGUILayout.IntField); + + return SafeTranslatorUtility.TryConvert(intValue, out uint newValue) ? newValue : value; + } + + public static bool RenderInput(ConfigFieldAttribute configFieldDetails, bool value, float labelWidth) + { + return InputRendererWrapper( + configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, + value, EditorGUILayout.Toggle); } - private static T InputRendererWrapper(string label, T value, float labelWidth, string toolTip, InputRenderDelegate renderFn) + public delegate T TestDelegate(GUIContent label, T value, params GUILayoutOption[] options); + + private static T InputRendererWithAlignedLabel(float labelWidth, Func renderFn) { - // Store the current label width so that it can be subsequently be restored. float currentLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = labelWidth; - T newValue = renderFn(label, value, labelWidth, toolTip); + T newValue = renderFn(); EditorGUIUtility.labelWidth = currentLabelWidth; return newValue; } + private static T InputRendererWrapper(string label, string toolTip, float labelWidth, T value, TestDelegate renderFn, string helpURL = null) + { + return InputRendererWithAlignedLabel(labelWidth, () => + { + if (!string.IsNullOrEmpty(helpURL)) + { + EditorGUILayout.BeginHorizontal(); + } + + T newValue = renderFn(CreateGUIContent(label, toolTip), value, GUILayout.ExpandWidth(true)); + + if (!string.IsNullOrEmpty(helpURL)) + { + RenderHelpIcon(helpURL); + EditorGUILayout.EndHorizontal(); + } + + return newValue; + }); + } + #endregion } } \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Utility/MakefileUtility.cs b/Assets/Plugins/Source/Editor/Utility/MakefileUtility.cs index 1c7d9418b..cfe55a7b0 100644 --- a/Assets/Plugins/Source/Editor/Utility/MakefileUtility.cs +++ b/Assets/Plugins/Source/Editor/Utility/MakefileUtility.cs @@ -42,7 +42,7 @@ static MakefileUtility() ErrorRegex = new Regex(@"error [a-zA-z0-9]*:", RegexOptions.IgnoreCase); } - [MenuItem("Tools/EOS Plugin/Build Libraries/Win32")] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Win32")] public static void BuildLibrariesWin32() { #if UNITY_EDITOR_WIN @@ -50,7 +50,7 @@ public static void BuildLibrariesWin32() #endif } - [MenuItem("Tools/EOS Plugin/Build Libraries/Win64")] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Win64")] public static void BuildLibrariesWin64() { #if UNITY_EDITOR_WIN @@ -58,8 +58,8 @@ public static void BuildLibrariesWin64() #endif } - [MenuItem("Tools/EOS Plugin/Build Libraries/Win32", true)] - [MenuItem("Tools/EOS Plugin/Build Libraries/Win64", true)] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Win32", true)] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Win64", true)] public static bool CanBuildLibrariesWindows() { #if UNITY_EDITOR_WIN @@ -69,7 +69,7 @@ public static bool CanBuildLibrariesWindows() #endif } - [MenuItem("Tools/EOS Plugin/Build Libraries/Mac")] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Mac")] public static void BuildLibrariesMac() { #if UNITY_EDITOR_OSX @@ -77,7 +77,7 @@ public static void BuildLibrariesMac() #endif } - [MenuItem("Tools/EOS Plugin/Build Libraries/Mac", true)] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Mac", true)] public static bool CanBuildLibrariesMac() { #if UNITY_EDITOR_OSX @@ -87,7 +87,7 @@ public static bool CanBuildLibrariesMac() #endif } - [MenuItem("Tools/EOS Plugin/Build Libraries/Linux")] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Linux")] public static void BuildLibrariesLinux() { #if UNITY_EDITOR_LINUX @@ -95,7 +95,7 @@ public static void BuildLibrariesLinux() #endif } - [MenuItem("Tools/EOS Plugin/Build Libraries/Linux", true)] + [MenuItem("EOS Plugin/Advanced/Build Libraries/Linux", true)] public static bool CanBuildLibrariesLinux() { #if UNITY_EDITOR_LINUX diff --git a/Assets/Plugins/Source/Editor/Utility/PackageFileUtility.cs b/Assets/Plugins/Source/Editor/Utility/PackageFileUtility.cs index 2189bf56c..89c69f4a5 100644 --- a/Assets/Plugins/Source/Editor/Utility/PackageFileUtility.cs +++ b/Assets/Plugins/Source/Editor/Utility/PackageFileUtility.cs @@ -20,19 +20,18 @@ * SOFTWARE. */ -using System.Collections.Generic; -using UnityEngine; -using System.IO; -using System; -using System.Text.RegularExpressions; - namespace PlayEveryWare.EpicOnlineServices.Utility { + using Common.Extensions; using Editor.Build; - using Extensions; + using System; + using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; + using UnityEngine; internal class PackageFileUtility { @@ -143,7 +142,7 @@ public static List FindPackageFiles(string root, Package private static IEnumerable FindMatchingFiles(string root, string currentWorkingDir, SrcDestPair pair) { IEnumerable collectedFiles; - + string searchPattern = pair.src; string path = root; @@ -288,7 +287,7 @@ public static async Task CopyFilesToDirectory( // Copy the files await FileSystemUtility.CopyFilesAsync(copyOperations, cancellationToken, progress); - + // Execute callback postProcessCallback?.Invoke(destination); } diff --git a/Assets/Plugins/Source/Editor/Utility/SigningUtility.cs b/Assets/Plugins/Source/Editor/Utility/SigningUtility.cs index 69d9a27ce..0565d5462 100644 --- a/Assets/Plugins/Source/Editor/Utility/SigningUtility.cs +++ b/Assets/Plugins/Source/Editor/Utility/SigningUtility.cs @@ -32,7 +32,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor public class SigningConfigUtility { - [MenuItem("Tools/EOS Plugin/Sign DLLs")] + [MenuItem("EOS Plugin/Advanced/Sign DLLs")] static async Task SignAllDLLs() { var signConfig = await EpicOnlineServices.Config.GetAsync(); @@ -49,7 +49,7 @@ static async Task SignAllDLLs() } } - [MenuItem("Tools/EOS Plugin/Sign DLLs", true)] + [MenuItem("EOS Plugin/Advanced/Sign DLLs", true)] static async Task CanSignDLLs() { #if UNITY_EDITOR_WIN diff --git a/Assets/Plugins/Windows/Core/WindowsConfig.cs b/Assets/Plugins/Windows/Core/WindowsConfig.cs index f3701c2b3..20906db81 100644 --- a/Assets/Plugins/Windows/Core/WindowsConfig.cs +++ b/Assets/Plugins/Windows/Core/WindowsConfig.cs @@ -25,7 +25,14 @@ namespace PlayEveryWare.EpicOnlineServices using System; // Flags specifically for Windows - [Serializable] + [ConfigGroup("Windows Config", new[] + { + "Windows-Specific Options", + "Deployment", + "Flags", + "Tick Budgets", + "Overlay Options" + }, false)] public class WindowsConfig : PlatformConfig { static WindowsConfig() diff --git a/Assets/Plugins/Windows/Core/WindowsPlatformSpecifics.cs b/Assets/Plugins/Windows/Core/WindowsPlatformSpecifics.cs index be55ec6c7..6b328d320 100644 --- a/Assets/Plugins/Windows/Core/WindowsPlatformSpecifics.cs +++ b/Assets/Plugins/Windows/Core/WindowsPlatformSpecifics.cs @@ -1,36 +1,51 @@ /* -* Copyright (c) 2021 PlayEveryWare -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// When compiled outside of Unity - there are some fields within this file +// that are never used. This suppresses those warnings - as the fact that they +// are unused is expected. +#if EXTERNAL_TO_UNITY +// Field is never used +#pragma warning disable CS0169 +// Field is assigned but its value is never used +#pragma warning disable CS0414 +// Field is never assigned to, and will always have its default value. +#pragma warning disable CS0649 +#endif #if !EOS_DISABLE //#define ENABLE_CONFIGURE_STEAM_FROM_MANAGED // If standalone windows and not editor, or the windows editor. -#if (UNITY_STANDALONE_WIN && !UNITY_EDITOR) || UNITY_EDITOR_WIN +#if (UNITY_STANDALONE_WIN && !UNITY_EDITOR) || UNITY_EDITOR_WIN || EXTERNAL_TO_UNITY namespace PlayEveryWare.EpicOnlineServices { using System.Collections.Generic; + +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif using Epic.OnlineServices.Platform; using System.Runtime.InteropServices; @@ -53,30 +68,33 @@ public class WindowsPlatformSpecifics : PlatformSpecifics public static string SteamConfigPath = "eos_steam_config.json"; #if ENABLE_CONFIGURE_STEAM_FROM_MANAGED - private static readonly string SteamDllName = + private static readonly string SteamDllName = #if UNITY_64 - "steam_api64.dll"; + "steam_api64.dll" #else - "steam_api.dll"; + "steam_api.dll" #endif + ; #endif private static GCHandle SteamOptionsGCHandle; - public WindowsPlatformSpecifics() : base(PlatformManager.Platform.Windows, ".dll") { } + public WindowsPlatformSpecifics() : base(PlatformManager.Platform.Windows) { } +#if !EXTERNAL_TO_UNITY //------------------------------------------------------------------------- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static public void Register() { EOSManagerPlatformSpecificsSingleton.SetEOSManagerPlatformSpecificsInterface(new WindowsPlatformSpecifics()); } +#endif //------------------------------------------------------------------------- public override void LoadDelegatesWithEOSBindingAPI() { // In the editor, EOS needs to be dynamically bound. -#if EOS_DYNAMIC_BINDINGS || UNITY_EDITOR +#if EOS_DYNAMIC_BINDINGS || UNITY_EDITOR const string EOSBinaryName = Epic.OnlineServices.Config.LibraryName; var eosLibraryHandle = EOSManager.EOSSingleton.LoadDynamicLibrary(EOSBinaryName); Epic.OnlineServices.WindowsBindings.Hook(eosLibraryHandle, (DLLHandle handle, string functionName) => { @@ -85,6 +103,7 @@ public override void LoadDelegatesWithEOSBindingAPI() #endif } +#if !EXTERNAL_TO_UNITY //------------------------------------------------------------------------- /// /// Nothing to be done on windows for the moment @@ -189,7 +208,21 @@ public override void ConfigureSystemPlatformCreateOptions(ref EOSCreateOptions c #endif } } + +#endif } } #endif #endif // !EOS_DISABLE + +// When compiled outside of Unity - there are some fields within this file +// that are never used. This suppresses those warnings - as the fact that they +// are unused is expected. +#if EXTERNAL_TO_UNITY +// Field is never used +#pragma warning restore CS0169 +// Field is assigned but its value is never used +#pragma warning restore CS0414 +// Field is never assigned to, and will always have its default value. +#pragma warning restore CS0649 +#endif \ No newline at end of file diff --git a/Assets/Plugins/Windows/x64/.gitignore b/Assets/Plugins/Windows/x64/.gitignore index 217482176..e36f681e0 100644 --- a/Assets/Plugins/Windows/x64/.gitignore +++ b/Assets/Plugins/Windows/x64/.gitignore @@ -11,3 +11,5 @@ DynamicLibraryLoaderHelper.ipdb DynamicLibraryLoaderHelper.ipdb.meta DynamicLibraryLoaderHelper.lib DynamicLibraryLoaderHelper.lib.meta + +ConsoleApplication* \ No newline at end of file diff --git a/Assets/Plugins/Windows/x64/DynamicLibraryLoaderHelper-x64.dll b/Assets/Plugins/Windows/x64/DynamicLibraryLoaderHelper-x64.dll index 3b5e5a6a6..350ad9b46 100644 --- a/Assets/Plugins/Windows/x64/DynamicLibraryLoaderHelper-x64.dll +++ b/Assets/Plugins/Windows/x64/DynamicLibraryLoaderHelper-x64.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4137246fe695d302dac3b9dae8f6c7fa84617bdef16d1973a7d4cd67b99502c4 -size 10752 +oid sha256:1172bc25ac4cac39887750eb40a3da76aaec09049544f4df5615b54ec5373302 +size 127488 diff --git a/Assets/Plugins/Windows/x64/GfxPluginNativeRender-x64.dll b/Assets/Plugins/Windows/x64/GfxPluginNativeRender-x64.dll index 5008a1b89..7d91cea62 100644 --- a/Assets/Plugins/Windows/x64/GfxPluginNativeRender-x64.dll +++ b/Assets/Plugins/Windows/x64/GfxPluginNativeRender-x64.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:333dc8a91c678c470fb7e9dd15c3ac4da7787af42e3f7468161f0cb48dc450fa -size 101376 +oid sha256:362ed5901eb650f579a34e2fd8ef8d06f6799b0f21577c446f5f09134962decf +size 583168 diff --git a/Assets/Plugins/Windows/x86/DynamicLibraryLoaderHelper-x86.dll b/Assets/Plugins/Windows/x86/DynamicLibraryLoaderHelper-x86.dll index 4f89799bf..7b86c3dda 100644 --- a/Assets/Plugins/Windows/x86/DynamicLibraryLoaderHelper-x86.dll +++ b/Assets/Plugins/Windows/x86/DynamicLibraryLoaderHelper-x86.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:866feddfad909ac1600ef529504bb7c9313a754207bbd1c71a09cb76e4b7bbcb -size 98816 +oid sha256:14c52cf4703afae3676342ceb2540e67e9ad1a1436ee880a4a4f89e41dc51225 +size 113152 diff --git a/Assets/Plugins/Windows/x86/GfxPluginNativeRender-x86.dll b/Assets/Plugins/Windows/x86/GfxPluginNativeRender-x86.dll index f1168eef3..456a4d46c 100644 --- a/Assets/Plugins/Windows/x86/GfxPluginNativeRender-x86.dll +++ b/Assets/Plugins/Windows/x86/GfxPluginNativeRender-x86.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adaca6c603194980d613586874fc1b8d4916052c7db5629c0086b675feec7963 -size 422400 +oid sha256:984f1619bf0fa7fbef099cadf639450134316b6143d87306e9f91ffb016159bb +size 440832 diff --git a/Assets/Plugins/iOS/Core/IOSConfig.cs b/Assets/Plugins/iOS/Core/IOSConfig.cs index 43c89e201..33a4311a2 100644 --- a/Assets/Plugins/iOS/Core/IOSConfig.cs +++ b/Assets/Plugins/iOS/Core/IOSConfig.cs @@ -22,10 +22,16 @@ namespace PlayEveryWare.EpicOnlineServices { - using System; - - // Flags specifically for iOS - [Serializable] + // Flags specifically for iOS. Note that labels for the baser + // PlatformConfig need to be specified here. + [ConfigGroup("EOS Config", new[] + { + "iOS-Specific Options", + "Deployment", + "Flags", + "Tick Budgets", + "Overlay Options" + }, false)] public class IOSConfig : PlatformConfig { static IOSConfig() diff --git a/Assets/Plugins/macOS/Core/MacOSConfig.cs b/Assets/Plugins/macOS/Core/MacOSConfig.cs index b4d529575..5c18acf08 100644 --- a/Assets/Plugins/macOS/Core/MacOSConfig.cs +++ b/Assets/Plugins/macOS/Core/MacOSConfig.cs @@ -25,7 +25,14 @@ namespace PlayEveryWare.EpicOnlineServices using System; // Flags specifically for macOS - [Serializable] + [ConfigGroup("MacOS Config", new[] + { + "MacOS-Specific Options", + "Deployment", + "Flags", + "Tick Budgets", + "Overlay Options" + }, false)] public class MacOSConfig : PlatformConfig { static MacOSConfig() diff --git a/Assets/Prefab/playerDataStorageUI.prefab b/Assets/Prefab/playerDataStorageUI.prefab index 2249b1bfc..8d748440e 100644 --- a/Assets/Prefab/playerDataStorageUI.prefab +++ b/Assets/Prefab/playerDataStorageUI.prefab @@ -1029,7 +1029,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0.00017978472} + m_AnchoredPosition: {x: 0, y: -0.000021546162} m_SizeDelta: {x: 0, y: 23.331116} m_Pivot: {x: 0, y: 1} --- !u!114 &7141925854171819916 @@ -1197,6 +1197,8 @@ GameObject: - component: {fileID: 1793095484309312535} - component: {fileID: 1793095484309312534} - component: {fileID: 3735133806406327909} + - component: {fileID: 2337753429563075375} + - component: {fileID: 4558143906080583195} m_Layer: 5 m_Name: uploadButton m_TagString: Untagged @@ -1339,6 +1341,35 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &2337753429563075375 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1793095484309312536} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 +--- !u!114 &4558143906080583195 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1793095484309312536} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + attachedTooltip: {fileID: 2337753429563075375} + attachedSelectable: {fileID: 1793095484309312534} --- !u!1 &1793095484465549111 GameObject: m_ObjectHideFlags: 0 @@ -1685,7 +1716,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 02c6faa6466a2b5479089c575a83289a, type: 3} m_Name: m_EditorClassIdentifier: - PlayerDataStorageUIParent: {fileID: 1793095484641389005} + UIFirstSelected: {fileID: 1793095485379140242} + UIParent: {fileID: 0} NewFileNameTextBox: {fileID: 239145686952381035} FilesContentParent: {fileID: 1793095483685924635} UIFileNameEntryPrefab: {fileID: 6120802170991890570, guid: 247c8c4e781b2e247a6d96b68687778f, @@ -1694,7 +1726,11 @@ MonoBehaviour: LocalViewText: {fileID: 3150005834901482316} CurrentFileNameText: {fileID: 4505900856109385762} AddItemDropdown: {fileID: 2097578950509207048} - UIFirstSelected: {fileID: 1793095485379140242} + addItemButton: {fileID: 8024301931214507978} + uploadButton: {fileID: 4558143906080583195} + downloadButton: {fileID: 4935098393252255257} + duplicateButton: {fileID: 3857393938778890016} + deleteButton: {fileID: 1820555442671209970} --- !u!1 &1793095484777068639 GameObject: m_ObjectHideFlags: 0 @@ -2154,7 +2190,7 @@ MonoBehaviour: m_HandleRect: {fileID: 1793095483305412063} m_Direction: 2 m_Value: 0 - m_Size: 0.9999208 + m_Size: 1 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -2676,6 +2712,8 @@ GameObject: - component: {fileID: 2214399829562284593} - component: {fileID: 2214399829562284594} - component: {fileID: 9058489209798759634} + - component: {fileID: 1820555442671209970} + - component: {fileID: 9111170552118360095} m_Layer: 5 m_Name: deleteButton m_TagString: Untagged @@ -2818,6 +2856,35 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &1820555442671209970 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2214399829562284588} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + attachedTooltip: {fileID: 9111170552118360095} + attachedSelectable: {fileID: 2214399829562284594} +--- !u!114 &9111170552118360095 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2214399829562284588} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 --- !u!1 &2214399830012201710 GameObject: m_ObjectHideFlags: 0 @@ -2831,6 +2898,8 @@ GameObject: - component: {fileID: 2214399830012201715} - component: {fileID: 2214399830012201708} - component: {fileID: 1055384770618046176} + - component: {fileID: 4935098393252255257} + - component: {fileID: 3113775767293338605} m_Layer: 5 m_Name: downloadButton m_TagString: Untagged @@ -2973,6 +3042,35 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &4935098393252255257 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2214399830012201710} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + attachedTooltip: {fileID: 3113775767293338605} + attachedSelectable: {fileID: 2214399830012201708} +--- !u!114 &3113775767293338605 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2214399830012201710} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 --- !u!1 &2214399830103315780 GameObject: m_ObjectHideFlags: 0 @@ -2986,6 +3084,8 @@ GameObject: - component: {fileID: 2214399830103315785} - component: {fileID: 2214399830103315786} - component: {fileID: 4078380609448125717} + - component: {fileID: 1427974435764745968} + - component: {fileID: 3857393938778890016} m_Layer: 5 m_Name: duplicateButton m_TagString: Untagged @@ -3128,6 +3228,35 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &1427974435764745968 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2214399830103315780} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 +--- !u!114 &3857393938778890016 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2214399830103315780} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + attachedTooltip: {fileID: 1427974435764745968} + attachedSelectable: {fileID: 2214399830103315786} --- !u!1 &2214399830132136930 GameObject: m_ObjectHideFlags: 0 @@ -3793,7 +3922,7 @@ MonoBehaviour: m_HandleRect: {fileID: 6198159106947253400} m_Direction: 0 m_Value: 0 - m_Size: 1 + m_Size: 0.9999999 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -4091,7 +4220,7 @@ MonoBehaviour: m_HandleRect: {fileID: 7652126183842441799} m_Direction: 0 m_Value: 0 - m_Size: 1 + m_Size: 0.9999999 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -5852,6 +5981,8 @@ GameObject: - component: {fileID: 8764718947428369138} - component: {fileID: 6333044505938080686} - component: {fileID: 7300275064290975394} + - component: {fileID: 8024301931214507978} + - component: {fileID: 6198293633270421894} m_Layer: 5 m_Name: addItemButton m_TagString: Untagged @@ -5995,6 +6126,35 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &8024301931214507978 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8336353868571798760} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + attachedTooltip: {fileID: 6198293633270421894} + attachedSelectable: {fileID: 6333044505938080686} +--- !u!114 &6198293633270421894 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8336353868571798760} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 --- !u!1 &8407310419292420169 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefab/titleStorageUI.prefab b/Assets/Prefab/titleStorageUI.prefab index 9748ec073..01a6bed2f 100644 --- a/Assets/Prefab/titleStorageUI.prefab +++ b/Assets/Prefab/titleStorageUI.prefab @@ -28,6 +28,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655369408980} m_Father: {fileID: 92279655674553523} @@ -104,6 +105,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657107303514} m_Father: {fileID: 92279656493458156} @@ -142,6 +144,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279656602892263} m_RootOrder: 0 @@ -216,6 +219,8 @@ GameObject: - component: {fileID: 92279655384530349} - component: {fileID: 92279655384530348} - component: {fileID: 4167894629628202064} + - component: {fileID: 501241579917481000} + - component: {fileID: 8606479818307076775} m_Layer: 5 m_Name: queryListButton m_TagString: Untagged @@ -233,6 +238,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657114166305} m_Father: {fileID: 92279657051232967} @@ -295,6 +301,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -356,6 +363,38 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &501241579917481000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 92279655384530350} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + EventToUpdateState: + m_PersistentCalls: + m_Calls: [] + attachedTooltip: {fileID: 8606479818307076775} + attachedSelectable: {fileID: 92279655384530348} +--- !u!114 &8606479818307076775 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 92279655384530350} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 --- !u!1 &92279655568194422 GameObject: m_ObjectHideFlags: 0 @@ -384,6 +423,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279656724099484} m_RootOrder: 0 @@ -460,6 +500,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 5010240655403608504} m_RootOrder: 0 @@ -560,6 +601,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656265997150} m_Father: {fileID: 92279656002219440} @@ -622,6 +664,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 0 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -685,6 +728,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655155962288} m_Father: {fileID: 92279656351925579} @@ -775,6 +819,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656459055223} m_Father: {fileID: 92279656002219440} @@ -866,6 +911,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656247166708} m_Father: {fileID: 4085046399809017193} @@ -928,6 +974,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -1019,6 +1066,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655693307973} - {fileID: 92279655641994149} @@ -1147,6 +1195,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279657175228306} m_RootOrder: 0 @@ -1222,6 +1271,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279657238414871} m_RootOrder: 0 @@ -1301,6 +1351,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279655775136433} m_RootOrder: 0 @@ -1378,6 +1429,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657145026107} m_Father: {fileID: 92279655641994149} @@ -1418,6 +1470,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655674553523} - {fileID: 92279656493458156} @@ -1545,6 +1598,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657051232967} - {fileID: 9038182995980843746} @@ -1568,7 +1622,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: fa7556926c51c0b47b00ac4011cc6fbf, type: 3} m_Name: m_EditorClassIdentifier: - TitleStorageUIParent: {fileID: 92279656380487525} + UIFirstSelected: {fileID: 92279657238414870} + UIParent: {fileID: 0} AddTagTextBox: {fileID: 2460614500089546019} FileNameTextBox: {fileID: 6461247679197728346} TagContentParent: {fileID: 92279656459055222} @@ -1578,7 +1633,6 @@ MonoBehaviour: UIFileNameEntryPrefab: {fileID: 6120802170991890570, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} FileContent: {fileID: 92279655291901358} - UIFirstSelected: {fileID: 92279657238414870} --- !u!1 &92279656459055222 GameObject: m_ObjectHideFlags: 0 @@ -1607,6 +1661,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655521222892} m_Father: {fileID: 92279655693307973} @@ -1686,6 +1741,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655200624256} m_Father: {fileID: 92279656351925579} @@ -1748,6 +1804,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 0 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -1810,6 +1867,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655291901345} m_Father: {fileID: 9038182995980843746} @@ -1887,6 +1945,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 7334623443727726163} m_RootOrder: 0 @@ -1984,6 +2043,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655568194423} m_Father: {fileID: 92279657129970517} @@ -2007,6 +2067,8 @@ GameObject: - component: {fileID: 92279656849646649} - component: {fileID: 92279656849646648} - component: {fileID: 9053787077613151453} + - component: {fileID: 5690088790579671396} + - component: {fileID: 1705523834735754496} m_Layer: 5 m_Name: downloadButton m_TagString: Untagged @@ -2024,6 +2086,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657172597506} m_Father: {fileID: 785517978497044233} @@ -2086,6 +2149,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -2147,6 +2211,38 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &5690088790579671396 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 92279656849646650} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 +--- !u!114 &1705523834735754496 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 92279656849646650} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + EventToUpdateState: + m_PersistentCalls: + m_Calls: [] + attachedTooltip: {fileID: 5690088790579671396} + attachedSelectable: {fileID: 92279656849646648} --- !u!1 &92279656888045456 GameObject: m_ObjectHideFlags: 0 @@ -2176,6 +2272,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1229780076036005320} m_RootOrder: 0 @@ -2275,6 +2372,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 9038182995980843746} m_RootOrder: 0 @@ -2353,6 +2451,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 7334623443727726163} - {fileID: 5010240655403608504} @@ -2421,6 +2520,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279655200624256} m_RootOrder: 0 @@ -2496,6 +2596,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279655384530351} m_RootOrder: 0 @@ -2576,6 +2677,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656724099484} m_Father: {fileID: 92279656351925579} @@ -2638,6 +2740,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 0 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -2700,6 +2803,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279656265997150} m_RootOrder: 0 @@ -2776,6 +2880,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657175228306} m_Father: {fileID: 92279656002219440} @@ -2838,6 +2943,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 0 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -2900,6 +3006,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 92279656849646651} m_RootOrder: 0 @@ -2977,6 +3084,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656081714526} m_Father: {fileID: 92279657147283261} @@ -3000,6 +3108,8 @@ GameObject: - component: {fileID: 92279657238414869} - component: {fileID: 92279657238414868} - component: {fileID: 6044818673693548901} + - component: {fileID: 6719133488062996990} + - component: {fileID: 7983512719310635204} m_Layer: 5 m_Name: addTagButton m_TagString: Untagged @@ -3017,6 +3127,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656094284081} m_Father: {fileID: 7334623443727726163} @@ -3079,6 +3190,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -3140,6 +3252,38 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!114 &6719133488062996990 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 92279657238414870} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd3e086d92861a8428faf740c1f97e6d, type: 3} + m_Name: + m_EditorClassIdentifier: + text: + position: 0 + PreferredWidth: -1 +--- !u!114 &7983512719310635204 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 92279657238414870} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: + EventToUpdateState: + m_PersistentCalls: + m_Calls: [] + attachedTooltip: {fileID: 6719133488062996990} + attachedSelectable: {fileID: 92279657238414868} --- !u!1 &478417506383690879 GameObject: m_ObjectHideFlags: 0 @@ -3168,6 +3312,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 6461247679197728358} - {fileID: 92279656849646651} @@ -3253,6 +3398,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279655582406761} - {fileID: 92279656002219440} @@ -3311,26 +3457,6 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: 1 m_LayoutPriority: 1 ---- !u!114 &3279977073878842924 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2460614500089546008} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} - m_Name: - m_EditorClassIdentifier: - m_IgnoreLayout: 0 - m_MinWidth: -1 - m_MinHeight: 20 - m_PreferredWidth: -1 - m_PreferredHeight: 30 - m_FlexibleWidth: 1 - m_FlexibleHeight: -1 - m_LayoutPriority: 1 --- !u!1 &3481114681183071521 GameObject: m_ObjectHideFlags: 0 @@ -3359,6 +3485,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656707216591} - {fileID: 2460614500089546015} @@ -3445,6 +3572,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 6677846320294157245} - {fileID: 92279655775136433} @@ -3530,6 +3658,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279656888045457} - {fileID: 92279656351925579} @@ -3615,6 +3744,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 6677846320294157245} m_RootOrder: 0 @@ -3666,26 +3796,6 @@ MonoBehaviour: m_VerticalOverflow: 0 m_LineSpacing: 1 m_Text: Add Platform Tag ---- !u!114 &1352403125623701948 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 6461247679197728353} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} - m_Name: - m_EditorClassIdentifier: - m_IgnoreLayout: 0 - m_MinWidth: -1 - m_MinHeight: 20 - m_PreferredWidth: -1 - m_PreferredHeight: 30 - m_FlexibleWidth: 1 - m_FlexibleHeight: -1 - m_LayoutPriority: 1 --- !u!1 &6597236637192343778 GameObject: m_ObjectHideFlags: 0 @@ -3712,6 +3822,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 92279657039396161} - {fileID: 92279656602892263} @@ -3753,6 +3864,7 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 414602307232107718} m_Father: {fileID: 4085046399809017193} @@ -3815,6 +3927,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -3894,110 +4007,121 @@ PrefabInstance: propertyPath: m_Name value: fileNameInputField objectReference: {fileID: 0} + - target: {fileID: 8911648707042544541, guid: 938b5e90c4f62df45a48e6b0b954e9f2, + type: 3} + propertyPath: CannotBeEmptyMessage + value: Empty file name cannot be downloaded. Please enter a filename to view + the contents of. + objectReference: {fileID: 0} + - target: {fileID: 8911648707042544541, guid: 938b5e90c4f62df45a48e6b0b954e9f2, + type: 3} + propertyPath: StateHandlerForSubmit + value: + objectReference: {fileID: 1705523834735754496} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalPosition.x - value: 0 + propertyPath: m_Pivot.x + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + propertyPath: m_Pivot.y + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalPosition.z + propertyPath: m_RootOrder value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.x - value: -0 + propertyPath: m_AnchorMax.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.y - value: -0 + propertyPath: m_AnchorMax.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.z - value: -0 + propertyPath: m_AnchorMin.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.w - value: 1 + propertyPath: m_AnchorMin.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_RootOrder + propertyPath: m_SizeDelta.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalEulerAnglesHint.x + propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalEulerAnglesHint.y + propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalEulerAnglesHint.z + propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchoredPosition.x + propertyPath: m_LocalPosition.z value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchoredPosition.y - value: 0 + propertyPath: m_LocalRotation.w + value: 1 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_SizeDelta.x - value: 0 + propertyPath: m_LocalRotation.x + value: -0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_SizeDelta.y - value: 0 + propertyPath: m_LocalRotation.y + value: -0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMin.x - value: 0 + propertyPath: m_LocalRotation.z + value: -0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMin.y + propertyPath: m_AnchoredPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMax.x + propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMax.y + propertyPath: m_LocalEulerAnglesHint.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_Pivot.x - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_Pivot.y - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544550, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} @@ -4006,24 +4130,44 @@ PrefabInstance: objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} +--- !u!114 &6461247679197728346 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 8911648707042544541, guid: 938b5e90c4f62df45a48e6b0b954e9f2, + type: 3} + m_PrefabInstance: {fileID: 2451787576570463687} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6461247679197728353} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3bf8bf3ee2656fe4e8492ab328e42ef1, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &6461247679197728353 stripped GameObject: m_CorrespondingSourceObject: {fileID: 8911648707042544550, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} m_PrefabInstance: {fileID: 2451787576570463687} m_PrefabAsset: {fileID: 0} ---- !u!114 &6461247679197728346 stripped +--- !u!114 &1352403125623701948 MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 8911648707042544541, guid: 938b5e90c4f62df45a48e6b0b954e9f2, - type: 3} - m_PrefabInstance: {fileID: 2451787576570463687} + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6461247679197728353} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3bf8bf3ee2656fe4e8492ab328e42ef1, type: 3} + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} m_Name: m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: 20 + m_PreferredWidth: -1 + m_PreferredHeight: 30 + m_FlexibleWidth: 1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 --- !u!224 &6461247679197728358 stripped RectTransform: m_CorrespondingSourceObject: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, @@ -4049,108 +4193,108 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalPosition.x - value: 0 + propertyPath: m_Pivot.x + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + propertyPath: m_Pivot.y + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalPosition.z + propertyPath: m_RootOrder value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalRotation.x + propertyPath: m_AnchorMax.x value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalRotation.y - value: 0 + propertyPath: m_AnchorMax.y + value: 1 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalRotation.z + propertyPath: m_AnchorMin.x value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalRotation.w + propertyPath: m_AnchorMin.y value: 1 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_RootOrder - value: 0 + propertyPath: m_SizeDelta.x + value: 136.68585 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 + propertyPath: m_SizeDelta.y + value: 16.234009 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalEulerAnglesHint.y + propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_LocalEulerAnglesHint.z + propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_AnchoredPosition.x - value: 78.342926 + propertyPath: m_LocalPosition.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_AnchoredPosition.y - value: -8.117004 + propertyPath: m_LocalRotation.w + value: 1 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_SizeDelta.x - value: 136.68585 + propertyPath: m_LocalRotation.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_SizeDelta.y - value: 16.234009 + propertyPath: m_LocalRotation.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_AnchorMin.x + propertyPath: m_LocalRotation.z value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_AnchorMin.y - value: 1 + propertyPath: m_AnchoredPosition.x + value: 78.342926 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_AnchorMax.x - value: 0 + propertyPath: m_AnchoredPosition.y + value: -8.117004 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_AnchorMax.y - value: 1 + propertyPath: m_LocalEulerAnglesHint.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_Pivot.x - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 5439077736924887059, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} - propertyPath: m_Pivot.y - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.z + value: 0 objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 9eedbc3c7133d6a4fae4a2b1b88e7e8e, type: 3} @@ -4169,108 +4313,108 @@ PrefabInstance: m_Modifications: - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalPosition.x - value: 0 + propertyPath: m_Pivot.x + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + propertyPath: m_Pivot.y + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalPosition.z + propertyPath: m_RootOrder value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalRotation.x + propertyPath: m_AnchorMax.x value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalRotation.y - value: 0 + propertyPath: m_AnchorMax.y + value: 1 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalRotation.z + propertyPath: m_AnchorMin.x value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalRotation.w + propertyPath: m_AnchorMin.y value: 1 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_RootOrder - value: 0 + propertyPath: m_SizeDelta.x + value: 260.05276 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 + propertyPath: m_SizeDelta.y + value: 23.331116 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalEulerAnglesHint.y + propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_LocalEulerAnglesHint.z + propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_AnchoredPosition.x - value: 135.02638 + propertyPath: m_LocalPosition.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_AnchoredPosition.y - value: -16.665558 + propertyPath: m_LocalRotation.w + value: 1 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_SizeDelta.x - value: 260.05276 + propertyPath: m_LocalRotation.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_SizeDelta.y - value: 23.331116 + propertyPath: m_LocalRotation.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_AnchorMin.x + propertyPath: m_LocalRotation.z value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_AnchorMin.y - value: 1 + propertyPath: m_AnchoredPosition.x + value: 135.02638 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_AnchorMax.x - value: 0 + propertyPath: m_AnchoredPosition.y + value: -16.665558 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_AnchorMax.y - value: 1 + propertyPath: m_LocalEulerAnglesHint.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_Pivot.x - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890569, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} - propertyPath: m_Pivot.y - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 6120802170991890570, guid: 247c8c4e781b2e247a6d96b68687778f, type: 3} @@ -4307,110 +4451,120 @@ PrefabInstance: propertyPath: m_Name value: addTagInputField objectReference: {fileID: 0} - - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, + - target: {fileID: 8911648707042544541, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalPosition.x - value: 0 + propertyPath: CannotBeEmptyMessage + value: Empty tag cannot be added. Please enter a tag name. objectReference: {fileID: 0} + - target: {fileID: 8911648707042544541, guid: 938b5e90c4f62df45a48e6b0b954e9f2, + type: 3} + propertyPath: StateHandlerForSubmit + value: + objectReference: {fileID: 7983512719310635204} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalPosition.y - value: 0 + propertyPath: m_Pivot.x + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalPosition.z - value: 0 + propertyPath: m_Pivot.y + value: 0.5 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.x - value: -0 + propertyPath: m_RootOrder + value: 1 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.y - value: -0 + propertyPath: m_AnchorMax.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.z - value: -0 + propertyPath: m_AnchorMax.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalRotation.w - value: 1 + propertyPath: m_AnchorMin.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_RootOrder - value: 1 + propertyPath: m_AnchorMin.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalEulerAnglesHint.x + propertyPath: m_SizeDelta.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalEulerAnglesHint.y + propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_LocalEulerAnglesHint.z + propertyPath: m_LocalPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchoredPosition.x + propertyPath: m_LocalPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchoredPosition.y + propertyPath: m_LocalPosition.z value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_SizeDelta.x - value: 0 + propertyPath: m_LocalRotation.w + value: 1 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_SizeDelta.y - value: 0 + propertyPath: m_LocalRotation.x + value: -0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMin.x - value: 0 + propertyPath: m_LocalRotation.y + value: -0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMin.y + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, + type: 3} + propertyPath: m_AnchoredPosition.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMax.x + propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_AnchorMax.y + propertyPath: m_LocalEulerAnglesHint.x value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_Pivot.x - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.y + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} - propertyPath: m_Pivot.y - value: 0.5 + propertyPath: m_LocalEulerAnglesHint.z + value: 0 objectReference: {fileID: 0} - target: {fileID: 8911648707042544550, guid: 938b5e90c4f62df45a48e6b0b954e9f2, type: 3} @@ -4425,6 +4579,26 @@ GameObject: type: 3} m_PrefabInstance: {fileID: 6451778726816665278} m_PrefabAsset: {fileID: 0} +--- !u!114 &3279977073878842924 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2460614500089546008} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: 20 + m_PreferredWidth: -1 + m_PreferredHeight: 30 + m_FlexibleWidth: 1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 --- !u!224 &2460614500089546015 stripped RectTransform: m_CorrespondingSourceObject: {fileID: 8911648707042544545, guid: 938b5e90c4f62df45a48e6b0b954e9f2, diff --git a/Assets/Scenes/StandardSamples/TitleStorage.unity b/Assets/Scenes/StandardSamples/TitleStorage.unity index 2ebf782ab..7360592f7 100644 --- a/Assets/Scenes/StandardSamples/TitleStorage.unity +++ b/Assets/Scenes/StandardSamples/TitleStorage.unity @@ -1612,6 +1612,21 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 6814204665588104691, guid: 11e4648022445f041b4c867ff8f3fd94, + type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6814204665588104691, guid: 11e4648022445f041b4c867ff8f3fd94, + type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6814204665588104691, guid: 11e4648022445f041b4c867ff8f3fd94, + type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} - target: {fileID: 7110259970179194835, guid: 11e4648022445f041b4c867ff8f3fd94, type: 3} propertyPath: m_AnchorMax.y @@ -1667,6 +1682,16 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 7521768095330256758, guid: 11e4648022445f041b4c867ff8f3fd94, + type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7521768095330256758, guid: 11e4648022445f041b4c867ff8f3fd94, + type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} - target: {fileID: 7908594211623330956, guid: 11e4648022445f041b4c867ff8f3fd94, type: 3} propertyPath: m_AnchorMax.y @@ -2441,6 +2466,41 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416019} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: MustHaveAtLeastOneTagValidator + objectReference: {fileID: 0} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.UITitleStorageMenu, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 501241579917481000, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 785517978497044233, guid: 8bbd61ca6afadd4489977f4a880223ba, type: 3} propertyPath: m_AnchorMax.y @@ -2501,6 +2561,41 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416017} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: FieldCannotBeEmptyValidator + objectReference: {fileID: 0} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.UIConsoleInputField, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 2460614500089546015, guid: 8bbd61ca6afadd4489977f4a880223ba, type: 3} propertyPath: m_AnchorMax.y @@ -2531,6 +2626,76 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416020} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416020} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: UpdateSelectableState + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: UpdateSelectableState + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} + - target: {fileID: 2460614500311340868, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 4085046399809017193, guid: 8bbd61ca6afadd4489977f4a880223ba, type: 3} propertyPath: m_AnchorMax.y @@ -2591,6 +2756,76 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416018} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416018} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: UpdateSelectableState + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: UpdateSelectableState + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnDidEndEdit.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} + - target: {fileID: 6461247678442183741, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: m_OnValueChanged.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} - target: {fileID: 6461247679197728358, guid: 8bbd61ca6afadd4489977f4a880223ba, type: 3} propertyPath: m_AnchorMax.y @@ -2681,6 +2916,41 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.size + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Mode + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Target + value: + objectReference: {fileID: 1997416016} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_CallState + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName + value: FieldCannotBeEmptyValidator + objectReference: {fileID: 0} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName + value: PlayEveryWare.EpicOnlineServices.Samples.UIConsoleInputField, com.playeveryware.eos.samples + objectReference: {fileID: 0} + - target: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + propertyPath: EventToUpdateState.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName + value: UnityEngine.Object, UnityEngine + objectReference: {fileID: 0} m_RemovedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 8bbd61ca6afadd4489977f4a880223ba, type: 3} --- !u!1 &1997416015 stripped @@ -2689,3 +2959,63 @@ GameObject: type: 3} m_PrefabInstance: {fileID: 1997416014} m_PrefabAsset: {fileID: 0} +--- !u!114 &1997416016 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 2460614500089546019, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + m_PrefabInstance: {fileID: 1997416014} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3bf8bf3ee2656fe4e8492ab328e42ef1, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1997416017 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 6461247679197728346, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + m_PrefabInstance: {fileID: 1997416014} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3bf8bf3ee2656fe4e8492ab328e42ef1, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1997416018 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 1705523834735754496, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + m_PrefabInstance: {fileID: 1997416014} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1997416019 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 92279656380487523, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + m_PrefabInstance: {fileID: 1997416014} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1997416015} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fa7556926c51c0b47b00ac4011cc6fbf, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1997416020 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 7983512719310635204, guid: 8bbd61ca6afadd4489977f4a880223ba, + type: 3} + m_PrefabInstance: {fileID: 1997416014} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58d58ade29043cc44ba216a367173ac5, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/Scripts/P2PNetcodeSample/Networking/EOSTransport.cs b/Assets/Scripts/P2PNetcodeSample/Networking/EOSTransport.cs index 11dae82c2..ea6ee8c47 100644 --- a/Assets/Scripts/P2PNetcodeSample/Networking/EOSTransport.cs +++ b/Assets/Scripts/P2PNetcodeSample/Networking/EOSTransport.cs @@ -52,9 +52,9 @@ public class EOSTransport : NetworkTransport public const ulong InvalidClientId = ulong.MaxValue; // Client ID Maps (locally persistent) - private ulong NextClientId = 1; // (ServerClientId is 0, so we start at 1) - private Dictionary ClientIdToUserId = null; - private Dictionary UserIdToClientId = null; + private ulong NextTransportId = 1; // (ServerClientId is 0, so we start at 1) + private Dictionary TransportIdToUserId = null; + private Dictionary UserIdToTransportId = null; // True if we're the Server, else we're a Client @@ -81,7 +81,13 @@ public class EOSTransport : NetworkTransport #endif /// /// A constant `clientId` that represents the server. - /// When this value is found in methods such as `Send`, it should be treated as a placeholder that means "the server". + /// When this value is found in methods such as `Send`, it should be + /// treated as a placeholder that means "the server". + /// + /// While most things inside EOSTransport have some nuanced difference + /// between a 'client id' and a 'transport id', this ServerClientId is + /// a special constant value. Both the client id and transport id of + /// the server will always be this value. /// public override ulong ServerClientId => 0; @@ -117,7 +123,13 @@ private void printError(string msg) /// /// Send a payload to the specified clientId, data and channelName. /// - /// The clientId to send to. + /// + /// The transport id to send to. + /// + /// This function maintains the parameter name 'clientId' because of its + /// base class, but this should be provided an associated transport id, + /// not the client's client id. + /// /// The data to send. /// The delivery type (QoS) to send data with. public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) @@ -157,7 +169,13 @@ public override void Send(ulong clientId, ArraySegment payload, NetworkDel /// /// Polls for incoming events, with an extra output parameter to report the precise time the event was received. /// - /// The clientId this event is for. + /// + /// The transportId this event is for. + /// + /// This function maintains the parameter name 'clientId' because of its + /// base class, but this should be provided an associated transport id, + /// not the client's client id. + /// /// The incoming data payload. /// The time the event was received, as reported by Time.realtimeSinceStartup. /// Returns the event type. @@ -187,7 +205,7 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment(packet); receiveTime = Time.realtimeSinceStartup; print($"EOSP2PTransport.PollEvent: [{NetworkEvent.Data}, ClientId='{clientId}', UserId='{userId}', PayloadBytes='{payload.Count}', RecvTimeSec='{receiveTime}']"); @@ -273,7 +291,12 @@ public override bool StartServer() /// /// Disconnects a client from the server. /// - /// The clientId to disconnect. + /// + /// The transport id to disconnect. + /// + /// This should be the transport id of the target user, not their client + /// id in Network Manager. + /// public override void DisconnectRemoteClient(ulong clientId) { Debug.Assert(IsInitialized); @@ -301,7 +324,12 @@ public override void DisconnectLocalClient() /// Gets the round trip time for a specific client. /// This method is optional, and not currently implemented in this case. /// - /// The clientId to get the RTT from. + /// + /// The transport id to get the RTT from. + /// + /// This should be the transport id of the target user, not their client + /// id in Network Manager. + /// /// 0 public override ulong GetCurrentRtt(ulong clientId) { @@ -344,8 +372,8 @@ public override void Shutdown() ConnectedDisconnectedUserEvents = null; // Clear ID maps - ClientIdToUserId = null; - UserIdToClientId = null; + TransportIdToUserId = null; + UserIdToTransportId = null; // Clear Server UserId target ServerUserId = null; @@ -367,9 +395,9 @@ public override void Initialize(NetworkManager networkManager) } // Create ID maps - NextClientId = 1; // Reset local client ID assignment counter - ClientIdToUserId = new Dictionary(); - UserIdToClientId = new Dictionary(); + NextTransportId = 1; // Reset local client ID assignment counter + TransportIdToUserId = new Dictionary(); + UserIdToTransportId = new Dictionary(); // Create user connects/disconnects event cache ConnectedDisconnectedUserEvents = new Queue>(); @@ -462,17 +490,17 @@ public void OnConnectionOpenedCallback(ProductUserId userId, string socketName) if (IsServer) { // We don't have this client in our map yet? (ie. We haven't seen them before) - if (UserIdToClientId.ContainsKey(userId) == false) + if (UserIdToTransportId.ContainsKey(userId) == false) { // Add client ID mapping - ulong newClientId = NextClientId++; // Generate new client ID (locally unique, incremental) - ClientIdToUserId.Add(newClientId, userId); - UserIdToClientId.Add(userId, newClientId); + ulong newClientId = NextTransportId++; // Generate new client ID (locally unique, incremental) + TransportIdToUserId.Add(newClientId, userId); + UserIdToTransportId.Add(userId, newClientId); } } // Get mapped client ID - ulong clientId = GetClientId(userId); + ulong clientId = GetTransportId(userId); // Cache user connection event, will be returned in a later PollEvent call ConnectedDisconnectedUserEvents.Enqueue(new Tuple(userId, clientId, true)); @@ -499,11 +527,11 @@ public void OnConnectionClosedCallback(ProductUserId userId, string socketName) if (IsServer) { // We should have seen this client before in a prior call to OnConnectionOpenedCallback - Debug.Assert(UserIdToClientId.ContainsKey(userId) == true); + Debug.Assert(UserIdToTransportId.ContainsKey(userId) == true); } // Get mapped client ID - ulong clientId = GetClientId(userId); + ulong clientId = GetTransportId(userId); // NOTE: For simplicity of event processing order and ID lookups we will simply allow the client ID map // to continually grow as we don't expect to receive an unreasonable number (>10k) of unique @@ -522,26 +550,72 @@ public void OnConnectionClosedCallback(ProductUserId userId, string socketName) } } - // Returns the ProductUserId corresponding to a given ClientId - private ProductUserId GetUserId(ulong clientId) + /// + /// Gets the ProductUserId for an associated transportId. + /// + /// As only a Server is able to know about clients, calling this + /// function from a client will always return . + /// + /// + /// The transport id of the user to look up. + /// Note that this is not the same as the "client id" inside + /// NetworkManager. + /// + /// The product user associated with the transport. + public ProductUserId GetUserId(ulong transportId) { Debug.Assert(IsInitialized); // We're a Client? if (IsServer == false) { - Debug.AssertFormat(clientId == ServerClientId, "EOSP2PTransport.GetUserId: Unexpected ClientId='{0}' given - We're a Client so we should only be dealing with the Server by definition (Server ClientId='{1}').", - clientId, ServerClientId); + Debug.AssertFormat(transportId == ServerClientId, "EOSP2PTransport.GetUserId: Unexpected ClientId='{0}' given - We're a Client so we should only be dealing with the Server by definition (Server ClientId='{1}').", + transportId, ServerClientId); return ServerUserId; } else { - return ClientIdToUserId[clientId]; + return TransportIdToUserId[transportId]; } } - // Returns the ClientId corresponding to a given ProductUserId - private ulong GetClientId(ProductUserId userId) + /// + /// Gets the TransportId for an associated ProductUserId. + /// + /// As only a Server is able to know about clients, calling this + /// function from a client will always return + /// . + /// + /// The term 'client id' and 'transport id' are related, but not exactly + /// the same. When a new client connects to a host, + /// assigns that user a client id. The + /// assigned (which is this class) is + /// responsible for identifying when a user joins, and assigning them + /// a transport id. NetworkManager and EOSTransport independently + /// assign these ids, and while they might coincide they are not + /// guaranteed to be the same. For example; if a user starts a client, + /// they might have a transport id that is the same as their client id. + /// But if they leave and return, EOSTransport will reuse the same + /// transport id, while NetworkManager will use a new client id. + /// + /// Most NetworkManager functions take in a client id. If it needs to + /// do something relating to the NetworkTransport, it will use an + /// internal lookup table to translate from the client id to transport + /// id, and then run the NetworkTransport function. Whenever + /// NetworkManager calls into NetworkTransport, it is always going to + /// provide a transport id, even if the parameter name in the transport + /// is 'clientId'. + /// + /// In order to manage clients, this function can be used to map from + /// ProductUserId to transport id. That transport id can then be used + /// to call public functions inside EOSTransport, which will usually + /// ripple up to NetworkManager to handle client id information. When + /// EOSTransport calls NetworkManager to run code, the NetworkManager + /// will usually translate the transport id back in to a client id. + /// + /// The EOS Product User to lookup. + /// The transport id associated with the user. + public ulong GetTransportId(ProductUserId userId) { Debug.Assert(IsInitialized); @@ -554,7 +628,7 @@ private ulong GetClientId(ProductUserId userId) } else { - return UserIdToClientId[userId]; + return UserIdToTransportId[userId]; } } } diff --git a/Assets/Scripts/P2PNetcodeSample/UI/P2PNetcode/UIP2PTransportMenu.cs b/Assets/Scripts/P2PNetcodeSample/UI/P2PNetcode/UIP2PTransportMenu.cs index 1dde5f86b..bbd2a56fa 100644 --- a/Assets/Scripts/P2PNetcodeSample/UI/P2PNetcode/UIP2PTransportMenu.cs +++ b/Assets/Scripts/P2PNetcodeSample/UI/P2PNetcode/UIP2PTransportMenu.cs @@ -434,10 +434,8 @@ private void RemoveJoinListener() } } - public override void Hide() + protected override void HideInternal() { - base.Hide(); - if (isClient || isHost) { DisconnectOnClick(); @@ -454,10 +452,8 @@ public override void Hide() RemoveJoinListener(); } - public override void Show() + protected override void ShowInternal() { - base.Show(); - Background.enabled = false; SetSessionUIActive(false); diff --git a/Assets/Scripts/StandardSamples/Oculus/OculusManager.cs b/Assets/Scripts/StandardSamples/Oculus/OculusManager.cs index 864b26b76..becb73482 100644 --- a/Assets/Scripts/StandardSamples/Oculus/OculusManager.cs +++ b/Assets/Scripts/StandardSamples/Oculus/OculusManager.cs @@ -24,9 +24,12 @@ #define DISABLEOCULUS #endif +#if !DISABLEOCULUS + using OculusWrapper = Oculus; // Don't forget to import Oculus.unitypackage from Legacy Oculus SDK +#endif + namespace PlayEveryWare.EpicOnlineServices.Samples.Oculus { - using System.Collections.Generic; using UnityEngine; using System; @@ -35,10 +38,6 @@ namespace PlayEveryWare.EpicOnlineServices.Samples.Oculus using System.Text; using System.Threading.Tasks; -#if !DISABLEOCULUS - using OculusWrapper = Oculus; //if erroring Dont forget to import Oculus' .unitypackage -#endif - [DisallowMultipleComponent] public class OculusManager : MonoBehaviour { diff --git a/Assets/Scripts/StandardSamples/Oculus/com.playeveryware.eos.samples.oculus.asmdef b/Assets/Scripts/StandardSamples/Oculus/com.playeveryware.eos.samples.oculus.asmdef index b94a687a1..5e0c7b6bd 100644 --- a/Assets/Scripts/StandardSamples/Oculus/com.playeveryware.eos.samples.oculus.asmdef +++ b/Assets/Scripts/StandardSamples/Oculus/com.playeveryware.eos.samples.oculus.asmdef @@ -2,7 +2,7 @@ "name": "com.playeveryware.eos.samples.oculus", "rootNamespace": "", "references": [ - "GUID:8be792bc795f6734c9b03442335cf985" + "GUID:e183dfe6fccb38f40bc810cdf9e63215" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/Scripts/StandardSamples/SignInWithGoogle.meta b/Assets/Scripts/StandardSamples/SignInWithGoogle.meta new file mode 100644 index 000000000..06b2db994 --- /dev/null +++ b/Assets/Scripts/StandardSamples/SignInWithGoogle.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a97820acee6eb1944ac138593f31ddbf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/StandardSamples/SignInWithGoogle/SignInWithGoogleManager.cs b/Assets/Scripts/StandardSamples/SignInWithGoogle/SignInWithGoogleManager.cs new file mode 100644 index 000000000..65414cb5c --- /dev/null +++ b/Assets/Scripts/StandardSamples/SignInWithGoogle/SignInWithGoogleManager.cs @@ -0,0 +1,103 @@ +/* +* Copyright (c) 2024 PlayEveryWare +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +using UnityEngine; + +namespace PlayEveryWare.EpicOnlineServices.Samples +{ + public class SignInWithGoogleManager : MonoBehaviour + { + AndroidJavaObject loginObject; + + public void GetGoogleIdToken(System.Action callback) + { + using AndroidJavaClass unityPlayer = new("com.unity3d.player.UnityPlayer"); + using AndroidJavaObject activity = unityPlayer.GetStatic("currentActivity"); + if (activity == null) + { + Debug.LogError("EOSAndroid: activity context is null!"); + return; + } + + using AndroidJavaClass loginClass = new AndroidJavaClass("com.playeveryware.googlelogin.login"); + if (loginClass == null) + { + Debug.LogError("Java Login Class is null!"); + return; + } + + loginObject = loginClass.CallStatic("instance"); + + /// Create the proxy class and pass instances to be used by the callback + EOSCredentialManagerCallback javaCallback = new EOSCredentialManagerCallback(); + javaCallback.loginObject = loginObject; + javaCallback.callback = callback; + + AndroidConfig config = AndroidConfig.Get(); + + if (string.IsNullOrEmpty(config.GoogleLoginClientID)) + { + Debug.LogError("Client ID is null, needs to be configured for Google ID connect login"); + return; + } + + /// SignInWithGoogle(String clientID, String nonce, Context context, CredentialManagerCallback callback) + loginObject.Call("SignInWithGoogle", config.GoogleLoginClientID, config.GoogleLoginNonce, activity, javaCallback); + } + + class EOSCredentialManagerCallback : AndroidJavaProxy + { + public AndroidJavaObject loginObject; + public System.Action callback; + + /// + /// Proxy class to receive Android callbacks in C# + /// + public EOSCredentialManagerCallback() : base("androidx.credentials.CredentialManagerCallback") { } + + /// + /// Succeeding Callback of GetCredentialAsync + /// GetCredentialAsync is called in com.playeveryware.googlelogin.login.SignInWithGoogle) + /// + /// + public void onResult(AndroidJavaObject credentialResponseResult) + { + /// Parses the response resilt into google credentials + loginObject.Call("handleSignIn", credentialResponseResult); + + /// Invoke Connect Login with fetched Google ID + callback.Invoke(loginObject.Call("getResultIdToken"), loginObject.Call("getResultName")); + } + + /// + /// Failing Callback of GetCredentialAsync + /// GetCredentialAsync is called in com.playeveryware.googlelogin.login.SignInWithGoogle) + /// + /// + public void onError(AndroidJavaObject credentialException) + { + loginObject.Call("handleFailure", credentialException); + callback.Invoke(null, null); + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Platforms/Android/AndroidConfigEditor.cs.meta b/Assets/Scripts/StandardSamples/SignInWithGoogle/SignInWithGoogleManager.cs.meta similarity index 83% rename from Assets/Plugins/Source/Editor/Platforms/Android/AndroidConfigEditor.cs.meta rename to Assets/Scripts/StandardSamples/SignInWithGoogle/SignInWithGoogleManager.cs.meta index 9ea8b6ab1..9fd775246 100644 --- a/Assets/Plugins/Source/Editor/Platforms/Android/AndroidConfigEditor.cs.meta +++ b/Assets/Scripts/StandardSamples/SignInWithGoogle/SignInWithGoogleManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 360f3ca41c3bab644ac4bf6d6b62d677 +guid: e8fae4a230eba934aa46fc609adbba40 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/StandardSamples/UI/Achievements/UIAchievementsMenu.cs b/Assets/Scripts/StandardSamples/UI/Achievements/UIAchievementsMenu.cs index fb81a9ea2..209d0c215 100644 --- a/Assets/Scripts/StandardSamples/UI/Achievements/UIAchievementsMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Achievements/UIAchievementsMenu.cs @@ -62,6 +62,12 @@ class AchievementData protected override void Awake() { + // Hide the Achievement Locked / Unlocked icons at the start, + // only making them active after we've fetched images for them. + // Otherwise, there will appear to be white squares in the sample. + achievementLockedIcon.gameObject.SetActive(false); + achievementUnlockedIcon.gameObject.SetActive(false); + base.Awake(); AchievementsService.Instance.Updated += OnAchievementDataUpdated; } @@ -212,6 +218,13 @@ public async void OnDefinitionIdButtonClicked(int i) var achievementData = achievementDataList[i]; var definition = achievementData.Definition; + + // Set both icons to be hidden + achievementLockedIcon.gameObject.SetActive(false); + achievementUnlockedIcon.gameObject.SetActive(false); + + // Asynchronously retrieve the icons and set the textures + // DisplayPlayerAchievement then will set the appropriate icon to be visible achievementUnlockedIcon.texture = await AchievementsService.Instance.GetAchievementUnlockedIconTexture(definition.AchievementId); achievementLockedIcon.texture = await AchievementsService.Instance.GetAchievementLockedIconTexture(definition.AchievementId); diff --git a/Assets/Scripts/StandardSamples/UI/Common/SampleMenu.cs b/Assets/Scripts/StandardSamples/UI/Common/SampleMenu.cs index b93591901..bdcb0f75e 100644 --- a/Assets/Scripts/StandardSamples/UI/Common/SampleMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Common/SampleMenu.cs @@ -24,6 +24,7 @@ namespace PlayEveryWare.EpicOnlineServices.Samples { + using Epic.OnlineServices; using System.Diagnostics; using System.Runtime.CompilerServices; using UnityEngine; @@ -217,13 +218,26 @@ protected virtual void Update() } /// - /// Shows the SampleMenu. If overriding, make sure to call this base - /// implementation first. + /// Shows the SampleMenu. + /// If this menu was not hidden, nothing happens. + /// If the menu was hidden, this calls . /// - public virtual void Show() + public void Show() { Log($"Show() started"); + if (RequiresAuthentication) + { + ProductUserId user = EOSManager.Instance.GetProductUserId(); + if (user == null || !user.IsValid()) + { + Log($"This SampleMenu requires authentication, and the user " + + "has not set a ProductUserId yet. Will not set this " + + "sample to visible until OnAuthenticationChanged."); + return; + } + } + // Don't do anything if already showing. if (!Hidden) return; @@ -255,14 +269,17 @@ public virtual void Show() // Flag as showing. Hidden = false; + ShowInternal(); + Log($"Show() completed"); } /// - /// Hides the SampleMenu. If overriding, make sure to call this base - /// implementation first. + /// Hides the SampleMenu. + /// If this menu was already hidden, nothing happens. + /// If the menu was not already hidden, this calls . /// - public virtual void Hide() + public void Hide() { Log($"Hide() started"); @@ -285,9 +302,35 @@ public virtual void Hide() // Flag as hidden. Hidden = true; + HideInternal(); + Log($"Hide() completed"); } + /// + /// If this menu is shown through successfully, then + /// this method is run so that implementing child classes can respond + /// to becoming available. + /// + protected virtual void ShowInternal() + { + // The default implementation of this function is blank; + // children can override it to run operations that are suitable + // for Show + } + + /// + /// If this menu is hidden through successfully, then + /// this method is run so that implementing child classes can respond + /// to becoming unavailable. + /// + protected virtual void HideInternal() + { + // The default implementation of this function is blank; + // children can override it to run operations that are suitable + // for Hide + } + /// /// Sets the focused control to be UIFirstSelected if doing so can be /// done. If reset is true, then ignore whether there already is a diff --git a/Assets/Scripts/StandardSamples/UI/Common/SelectableStateHandler.cs b/Assets/Scripts/StandardSamples/UI/Common/SelectableStateHandler.cs new file mode 100644 index 000000000..d342b3235 --- /dev/null +++ b/Assets/Scripts/StandardSamples/UI/Common/SelectableStateHandler.cs @@ -0,0 +1,188 @@ +/* +* Copyright (c) 2021 PlayEveryWare +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +namespace PlayEveryWare.EpicOnlineServices.Samples +{ + using System.Collections; + using UnityEngine; + using UnityEngine.UI; + using UnityEngine.EventSystems; + using System; + using UnityEngine.Events; + + /// + /// Manages components. + /// + /// Using the event assigned in , when this + /// object's is changed it will update the Selectable + /// identified in 's interactable boolean. + /// This can also point to a to set the text of + /// the tooltip when updating state. + /// + public class SelectableStateHandler : MonoBehaviour + { + /// + /// Represents a state for this component, indicating if it should be + /// interactable and what tooltip should be displayed when + /// moused over. + /// + public struct InteractableState + { + /// + /// Indication of whether the associated Selectable should be + /// interactable. + /// + public bool Interactable; + + /// + /// The tooltip text that should be displayed on mouse over. + /// If empty, a tooltip will not be displayed. + /// + public string TooltipText; + + public InteractableState(bool shouldBeInteractable, string newTooltipText = "") + { + Interactable = shouldBeInteractable; + TooltipText = newTooltipText; + } + } + + /// + /// The current state of the interactable this is attached to. + /// This should be updated in the events assigned to + /// . + /// + /// It cannot be guaranteed that this is updated at the appropriate time. + /// + public InteractableState State + { + get + { + return _state; + } + set + { + _state = value; + ApplySelectableStateChange(); + } + } + + private InteractableState _state = new InteractableState(false); + + /// + /// Event raised in that informs + /// all instances of this component to update their state through + /// . + /// + private static event Action StateUpdated; + + /// + /// When is raised, this event is called to + /// allow manager code to update this component's . + /// It is assumed manager code will update + /// appropriately inside the function assigned to this in the inspector. + /// + /// This design paradigm allows inspector time configuration of state + /// management. + /// + [SerializeField] + private UnityEvent EventToUpdateState; + + /// + /// The attached UITooltip component, which has its text set by + /// 's . + /// + /// Optional. If not present, no tooltip will be updated. + /// + [SerializeReference] + private UITooltip attachedTooltip; + + /// + /// The associated Unity Selectable UI element this is associated with. + /// is set by 's + /// value. + /// + /// While optional, the purpose of this component is to augment + /// Selectables. + /// + [SerializeReference] + private Selectable attachedSelectable; + + private void OnEnable() + { + StateUpdated += UpdateSelectableState; + UpdateSelectableState(); + } + + private void OnDisable() + { + StateUpdated -= UpdateSelectableState; + } + + /// + /// Updates this component's state through raising + /// , which is presumed to contain + /// functions that will update . The setter in + /// will then update the visual UI. + /// + /// This is raised by . As a public + /// function, elements in the inspector could directly call this function + /// as part of some event in order to update specific components. + /// + public void UpdateSelectableState() + { + EventToUpdateState.Invoke(this); + } + + /// + /// When has its setter run, this handles applying + /// the state to the UI. + /// + private void ApplySelectableStateChange() + { + if (attachedTooltip != null) + { + if (!string.IsNullOrEmpty(State.TooltipText)) + { + attachedTooltip.Text = State.TooltipText; + } + else + { + attachedTooltip.Text = string.Empty; + } + } + + if (attachedSelectable != null) + { + attachedSelectable.interactable = State.Interactable; + } + } + + /// + /// Notifies all instances of this component to update their state. + /// + public static void NotifySelectableUpdate() + { + StateUpdated?.Invoke(); + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Platforms/macOS/MacOSConfigEditor.cs.meta b/Assets/Scripts/StandardSamples/UI/Common/SelectableStateHandler.cs.meta similarity index 83% rename from Assets/Plugins/Source/Editor/Platforms/macOS/MacOSConfigEditor.cs.meta rename to Assets/Scripts/StandardSamples/UI/Common/SelectableStateHandler.cs.meta index 567cec9ff..414f12b14 100644 --- a/Assets/Plugins/Source/Editor/Platforms/macOS/MacOSConfigEditor.cs.meta +++ b/Assets/Scripts/StandardSamples/UI/Common/SelectableStateHandler.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3642e24bc7c66d84ea5fe356c8487c7e +guid: 58d58ade29043cc44ba216a367173ac5 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/StandardSamples/UI/Common/UITooltip.cs b/Assets/Scripts/StandardSamples/UI/Common/UITooltip.cs index dbb9bf73e..ad25e7662 100644 --- a/Assets/Scripts/StandardSamples/UI/Common/UITooltip.cs +++ b/Assets/Scripts/StandardSamples/UI/Common/UITooltip.cs @@ -87,7 +87,7 @@ private void Hide() public void OnPointerEnter(PointerEventData eventData) { - if (UITooltipManager.Instance != null) + if (UITooltipManager.Instance != null && !string.IsNullOrEmpty(text)) { tooltipTimer = StartCoroutine(ShowTooltipWithDelay(UITooltipManager.Instance.HoverTime)); } diff --git a/Assets/Scripts/StandardSamples/UI/Common/UITooltipManager.cs b/Assets/Scripts/StandardSamples/UI/Common/UITooltipManager.cs index ca2cc66f9..f3834c755 100644 --- a/Assets/Scripts/StandardSamples/UI/Common/UITooltipManager.cs +++ b/Assets/Scripts/StandardSamples/UI/Common/UITooltipManager.cs @@ -303,8 +303,8 @@ public void HideTooltip(UITooltip tooltipComp) if (tooltipComp == currentTooltip) { TooltipContainer.gameObject.SetActive(false); + currentTooltip = null; } - currentTooltip = null; } } } \ No newline at end of file diff --git a/Assets/Scripts/StandardSamples/UI/CustomInvites/UICustomInvitesMenu.cs b/Assets/Scripts/StandardSamples/UI/CustomInvites/UICustomInvitesMenu.cs index 5d758ac0b..6fed2181e 100644 --- a/Assets/Scripts/StandardSamples/UI/CustomInvites/UICustomInvitesMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/CustomInvites/UICustomInvitesMenu.cs @@ -62,18 +62,16 @@ protected override void OnDestroy() EOSManager.Instance.RemoveManager(); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); if (EOSManager.Instance.GetProductUserId()?.IsValid() == true) { CustomInvitesManager?.ClearPayload(); } } - public override void Show() + protected override void ShowInternal() { - base.Show(); PayloadInputField.InputField.text = string.Empty; CustomInvitesManager.ClearPayload(); diff --git a/Assets/Scripts/StandardSamples/UI/Friends/UIFriendsMenu.cs b/Assets/Scripts/StandardSamples/UI/Friends/UIFriendsMenu.cs index 8b9987da7..727daeb8e 100644 --- a/Assets/Scripts/StandardSamples/UI/Friends/UIFriendsMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Friends/UIFriendsMenu.cs @@ -301,15 +301,13 @@ public void RefreshFriendsOnClick() FriendsManager.QueryFriends(null); } - public override void Show() + protected override void ShowInternal() { - base.Show(); EOSManager.Instance.GetOrCreateManager().OnLoggedIn(); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); FriendsManager?.OnLoggedOut(); } } diff --git a/Assets/Scripts/StandardSamples/UI/Leaderboard/UILeaderboardMenu.cs b/Assets/Scripts/StandardSamples/UI/Leaderboard/UILeaderboardMenu.cs index 3228e79b1..0bf5b6588 100644 --- a/Assets/Scripts/StandardSamples/UI/Leaderboard/UILeaderboardMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Leaderboard/UILeaderboardMenu.cs @@ -358,9 +358,8 @@ public async void IngestStatOnClick() refreshLeaderboardCoroutine = StartCoroutine(RefreshCurrentLeaderboardAfterWait(SecondsAfterStatIngestedToRefresh)); } - public override void Show() + protected override void ShowInternal() { - base.Show(); Invoke(nameof(InitFriends), 0); } diff --git a/Assets/Scripts/StandardSamples/UI/Lobbies/UILobbiesMenu.cs b/Assets/Scripts/StandardSamples/UI/Lobbies/UILobbiesMenu.cs index 76bf9d058..3a0523b32 100644 --- a/Assets/Scripts/StandardSamples/UI/Lobbies/UILobbiesMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Lobbies/UILobbiesMenu.cs @@ -763,16 +763,14 @@ public void UIUpateSearchResults(Result result) } } } - - public override void Show() + + protected override void ShowInternal() { - base.Show(); EOSManager.Instance.GetOrCreateManager().OnLoggedIn(); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); LobbyManager?.OnLoggedOut(); } diff --git a/Assets/Scripts/StandardSamples/UI/Login/UILoginMenu.cs b/Assets/Scripts/StandardSamples/UI/Login/UILoginMenu.cs index 2e09258c5..d5d60fdf7 100644 --- a/Assets/Scripts/StandardSamples/UI/Login/UILoginMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Login/UILoginMenu.cs @@ -687,11 +687,15 @@ private void ConfigureUIForConnectLogin() { //case ExternalCredentialType.GogSessionTicket: - //case ExternalCredentialType.GoogleIdToken: //case ExternalCredentialType.ItchioJwt: //case ExternalCredentialType.ItchioKey: //case ExternalCredentialType.AmazonAccessToken: - +#if !UNITY_ANDROID || UNITY_EDITOR + case ExternalCredentialType.GoogleIdToken: + loginButton.interactable = false; + loginButtonText.text = "Platform not supported."; + break; +#endif #if !(UNITY_STANDALONE) case ExternalCredentialType.SteamSessionTicket: case ExternalCredentialType.SteamAppTicket: @@ -1074,7 +1078,7 @@ public void OnLoginButtonClick() { EOSManager.Instance.StartLoginWithLoginTypeAndToken(loginType, null, - EOSManager.Instance.GetCommandLineArgsFromEpicLauncher().authPassword, + EOSManager.EOSSingleton.GetCommandLineArgsFromEpicLauncher().authPassword, StartLoginWithLoginTypeAndTokenCallback); } else @@ -1093,6 +1097,10 @@ private void AcquireTokenForConnectLogin(ExternalCredentialType externalType) { switch (externalType) { + case ExternalCredentialType.GoogleIdToken: + ConnectGoogleId(); + break; + case ExternalCredentialType.SteamSessionTicket: ConnectSteamSessionTicket(); break; @@ -1171,6 +1179,22 @@ private void CreateDeviceCallback(ref Epic.OnlineServices.Connect.CreateDeviceId } } + private void ConnectGoogleId() + { + SignInWithGoogleManager signInWithGoogleManager = new(); + + signInWithGoogleManager.GetGoogleIdToken((string token, string username) => + { + if (string.IsNullOrEmpty(token)) + { + Debug.LogError("Failed to retrieve Google Id Token"); + return; + } + + StartConnectLoginWithToken(ExternalCredentialType.GoogleIdToken, token, username); + }); + } + private void ConnectAppleId() { signInWithAppleManager = new Apple.EOSSignInWithAppleManager(); diff --git a/Assets/Scripts/StandardSamples/UI/MatchMaking/UISessionsMatchmakingMenu.cs b/Assets/Scripts/StandardSamples/UI/MatchMaking/UISessionsMatchmakingMenu.cs index fa2d82b29..89cdff359 100644 --- a/Assets/Scripts/StandardSamples/UI/MatchMaking/UISessionsMatchmakingMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/MatchMaking/UISessionsMatchmakingMenu.cs @@ -369,9 +369,8 @@ public void DeclineInviteButtonOnClick() GetEOSSessionsManager.DeclineLobbyInvite(); } - public override void Show() + protected override void ShowInternal() { - base.Show(); GetEOSSessionsManager.OnLoggedIn(); GetEOSSessionsManager.OnPresenceChange.AddListener(SetDirtyFlagAction); @@ -379,9 +378,8 @@ public override void Show() EOSManager.Instance.SetLogLevel(Epic.OnlineServices.Logging.LogCategory.Sessions, Epic.OnlineServices.Logging.LogLevel.Verbose); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); if (GetEOSSessionsManager.IsUserLoggedIn)//check to prevent warnings when done unnecessarily during Sessions & Matchmaking startup { GetEOSSessionsManager.OnPresenceChange.RemoveListener(SetDirtyFlagAction); diff --git a/Assets/Scripts/StandardSamples/UI/Metrics/UIMetricsMenu.cs b/Assets/Scripts/StandardSamples/UI/Metrics/UIMetricsMenu.cs index e6de9cd00..79e415803 100644 --- a/Assets/Scripts/StandardSamples/UI/Metrics/UIMetricsMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Metrics/UIMetricsMenu.cs @@ -45,8 +45,20 @@ public class UIMetricsMenu : SampleMenu private EOSMetricsManager MetricsManager; private EOSUserInfoManager UserInfoManager; + private bool Initialized { get; set; } = false; + private void Start() { + Initialize(); + } + + private void Initialize() + { + if (Initialized) + { + return; + } + MetricsManager = EOSManager.Instance.GetOrCreateManager(); UserInfoManager = EOSManager.Instance.GetOrCreateManager(); @@ -60,6 +72,8 @@ private void Start() ErrorMessageTxt.gameObject.SetActive(false); UpdateButtons(); + + Initialized = true; } protected override void OnDestroy() @@ -103,6 +117,12 @@ private bool CheckError() private void UpdateButtons() { + // If this update was requested before the menu is set up, exit early + if (MetricsManager == null) + { + return; + } + bool sessionActive = MetricsManager.IsSessionActive(); BeginBtn.interactable = !sessionActive; EndBtn.interactable = sessionActive; @@ -146,15 +166,15 @@ public void EndSessionOnClick() UpdateButtons(); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); UpdateButtons(); } - public override void Show() + protected override void ShowInternal() { - base.Show(); + Initialize(); + var localUserInfo = UserInfoManager.GetLocalUserInfo(); if (localUserInfo.UserId?.IsValid() == true) { diff --git a/Assets/Scripts/StandardSamples/UI/Peer2Peer/HighFrequency/UIHighFrequencyPeer2PeerMenu.cs b/Assets/Scripts/StandardSamples/UI/Peer2Peer/HighFrequency/UIHighFrequencyPeer2PeerMenu.cs index a40d8e586..c1594f799 100644 --- a/Assets/Scripts/StandardSamples/UI/Peer2Peer/HighFrequency/UIHighFrequencyPeer2PeerMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Peer2Peer/HighFrequency/UIHighFrequencyPeer2PeerMenu.cs @@ -230,9 +230,8 @@ public void SendOnClick() ChatMessageInput.InputField.text = string.Empty; } - public override void Show() + protected override void ShowInternal() { - base.Show(); EOSManager.Instance.GetOrCreateManager().OnLoggedIn(); var presenceInterface = EOSManager.Instance.GetEOSPresenceInterface(); @@ -277,9 +276,8 @@ public void SetPacketSize(string mb) Debug.Log("UIPeer2PeerMenu (SetPacketSize):Updated packet size to " + Peer2PeerManager.packetSizeMB + " Mb."); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); Peer2PeerManager?.OnLoggedOut(); } diff --git a/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIConsoleInputField.cs b/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIConsoleInputField.cs index d891c73ea..86409324c 100644 --- a/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIConsoleInputField.cs +++ b/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIConsoleInputField.cs @@ -25,9 +25,11 @@ namespace PlayEveryWare.EpicOnlineServices.Samples using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; - + using static PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler; + public class UIConsoleInputField : MonoBehaviour { + public string CannotBeEmptyMessage = "Field cannot be empty."; public Button InputFieldButton; public InputField InputField; @@ -40,10 +42,7 @@ public class UIConsoleInputField : MonoBehaviour private void Awake() { InputField.onEndEdit.AddListener(OnEndEdit); - -#if UNITY_ANDROID InputField.onValueChanged.AddListener(OnEdit); -#endif } #if UNITY_ANDROID @@ -61,14 +60,18 @@ private void Update() } prevKeyboardStatus = kbStatus; } +#endif private void OnEdit(string currentText) { +#if UNITY_ANDROID oldEditText = editText; editText = currentText; - } #endif + SelectableStateHandler.NotifySelectableUpdate(); + } + public void OnEndEdit(string value) { if (EventSystem.current != null && !EventSystem.current.alreadySelecting @@ -88,6 +91,8 @@ public void OnEndEdit(string value) keepOldTextInField = false; } #endif + + SelectableStateHandler.NotifySelectableUpdate(); } public void InputFieldOnClick() @@ -117,5 +122,17 @@ private void OnKeyboardCompleted(string result) // Return focus to button InputField.onEndEdit.Invoke(result); } + + public void FieldCannotBeEmptyValidator(SelectableStateHandler toUpdate) + { + if (string.IsNullOrEmpty(InputField.text)) + { + toUpdate.State = new InteractableState(false, CannotBeEmptyMessage); + } + else + { + toUpdate.State = new InteractableState(true); + } + } } } \ No newline at end of file diff --git a/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIPeer2PeerMenu.cs b/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIPeer2PeerMenu.cs index 55aa9fe18..287c4dec8 100644 --- a/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIPeer2PeerMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Peer2Peer/UIPeer2PeerMenu.cs @@ -279,9 +279,8 @@ public void SendOnClick() ChatMessageInput.InputField.text = string.Empty; } - public override void Show() + protected override void ShowInternal() { - base.Show(); EOSManager.Instance.GetOrCreateManager().OnLoggedIn(); var presenceInterface = EOSManager.Instance.GetEOSPresenceInterface(); @@ -313,10 +312,8 @@ public override void Show() } - public override void Hide() + protected override void HideInternal() { - base.Hide(); - Peer2PeerManager?.OnLoggedOut(); CloseChatOnClick(); diff --git a/Assets/Scripts/StandardSamples/UI/PlayerReports/UIPlayerReportMenu.cs b/Assets/Scripts/StandardSamples/UI/PlayerReports/UIPlayerReportMenu.cs index 6e0d4c4a2..48f57e397 100644 --- a/Assets/Scripts/StandardSamples/UI/PlayerReports/UIPlayerReportMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/PlayerReports/UIPlayerReportMenu.cs @@ -189,15 +189,13 @@ public override void OnFriendInteractButtonClicked(FriendData friendData) ReportButtonOnClick(friendData.UserProductUserId, friendData.Name); } - public override void Show() + protected override void ShowInternal() { - base.Show(); ResetPopUp(); } - public override void Hide() + protected override void HideInternal() { - base.Hide(); ResetPopUp(); } } diff --git a/Assets/Scripts/StandardSamples/UI/Storage/Player/UIPlayerDataStorageMenu.cs b/Assets/Scripts/StandardSamples/UI/Storage/Player/UIPlayerDataStorageMenu.cs index f7ba80ad3..744a42ec3 100644 --- a/Assets/Scripts/StandardSamples/UI/Storage/Player/UIPlayerDataStorageMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Storage/Player/UIPlayerDataStorageMenu.cs @@ -26,6 +26,7 @@ namespace PlayEveryWare.EpicOnlineServices.Samples using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; + using static PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler; using JsonUtility = Utility.JsonUtility; [Serializable] @@ -222,6 +223,8 @@ public void DeleteButtonOnClick() currentSelectedFile = string.Empty; CurrentFileNameText.text = "*No File Selected*"; + + SelectableStateHandler.NotifySelectableUpdate(); } private void FileListOnClick(string fileName) @@ -240,6 +243,8 @@ private void FileListOnClick(string fileName) UpdateRemoteView(fileName); currentInventory = null; LocalViewText.text = "*** Click Download button to create a local copy to modify ***"; + + SelectableStateHandler.NotifySelectableUpdate(); } private void UpdateRemoteView(string fileName) @@ -289,19 +294,37 @@ public void AddItemOnClick() LocalViewText.text = JsonUtility.ToJson(currentInventory, true); } - public override void Show() + protected override void ShowInternal() { - base.Show(); UpdateFileListUI(); PlayerDataStorageService.Instance.OnFileListUpdated += UpdateFileListUI; } - public override void Hide() + protected override void HideInternal() { - base.Hide(); - currentSelectedFile = string.Empty; currentInventory = null; + PlayerDataStorageService.Instance.OnFileListUpdated -= UpdateFileListUI; + } + + protected override void OnDestroy() + { + base.OnDestroy(); + PlayerDataStorageService.Instance.OnFileListUpdated -= UpdateFileListUI; + } + + public void NeedSelectedFileHandler(SelectableStateHandler toUpdate) + { + const string NeedToSelectAFileFirst = "Need to select a file first"; + + if (string.IsNullOrEmpty(currentSelectedFile)) + { + toUpdate.State = new InteractableState(false, NeedToSelectAFileFirst); + } + else + { + toUpdate.State = new InteractableState(true); + } } } } \ No newline at end of file diff --git a/Assets/Scripts/StandardSamples/UI/Storage/Title/UITitleStorageMenu.cs b/Assets/Scripts/StandardSamples/UI/Storage/Title/UITitleStorageMenu.cs index 72935cad3..ab422540e 100644 --- a/Assets/Scripts/StandardSamples/UI/Storage/Title/UITitleStorageMenu.cs +++ b/Assets/Scripts/StandardSamples/UI/Storage/Title/UITitleStorageMenu.cs @@ -28,6 +28,7 @@ namespace PlayEveryWare.EpicOnlineServices.Samples using Epic.OnlineServices; using EpicOnlineServices; using Config = PlayEveryWare.EpicOnlineServices.Config; + using static PlayEveryWare.EpicOnlineServices.Samples.SelectableStateHandler; /// /// Unity UI sample that uses TitleStoragemanager to demo features. Can be used as a template or starting point for implementing Title Storage features. @@ -48,23 +49,6 @@ public class UITitleStorageMenu : SampleMenu public Text FileContent; private List CurrentTags = new List(); - protected override void Awake() - { - base.Awake(); - if (Config.Get().IsEncryptionKeyValid()) - { - FileContent.text = string.Empty; - } - else - { - FileContent.text = "Valid encryption key not set. Use the EOS Config Editor to add one."; - } - } - - private void Start() - { - } - public void AddTagOnClick() { if (AddTag(AddTagTextBox.InputField.text)) @@ -82,6 +66,8 @@ public void ClearTagsOnClick() { GameObject.Destroy(child.gameObject); } + + SelectableStateHandler.NotifySelectableUpdate(); } public void AddPlatformTagOnClick() @@ -137,6 +123,8 @@ private bool AddTag(string tag) tagEntry.TagTxt.text = tag; } + SelectableStateHandler.NotifySelectableUpdate(); + return true; } @@ -227,5 +215,19 @@ public void UpdateFileContent(Result result) Debug.LogErrorFormat("UITitleStorageMenu - '{0}' file content was not found in cached data storage.", FileNameTextBox.InputField.text); } } + + public void MustHaveAtLeastOneTagValidator(SelectableStateHandler toUpdate) + { + const string MustHaveAtLeastOneTagMessage = "Please enter at least one tag and press 'Add tag'."; + + if (CurrentTags.Count == 0) + { + toUpdate.State = new InteractableState(false, MustHaveAtLeastOneTagMessage); + } + else + { + toUpdate.State = new InteractableState(true); + } + } } } \ No newline at end of file diff --git a/Assets/Scripts/StandardSamples/Utility/Extensions/ExternalCredentialTypeExtensions.cs b/Assets/Scripts/StandardSamples/Utility/Extensions/ExternalCredentialTypeExtensions.cs index 702cce0ab..f30cf1774 100644 --- a/Assets/Scripts/StandardSamples/Utility/Extensions/ExternalCredentialTypeExtensions.cs +++ b/Assets/Scripts/StandardSamples/Utility/Extensions/ExternalCredentialTypeExtensions.cs @@ -31,6 +31,7 @@ public static bool IsDemoed(this ExternalCredentialType externalCredentialType) switch (externalCredentialType) { // These credential types are demonstrated in the samples + case ExternalCredentialType.GoogleIdToken: case ExternalCredentialType.SteamAppTicket: case ExternalCredentialType.DiscordAccessToken: case ExternalCredentialType.OpenidAccessToken: @@ -40,7 +41,6 @@ public static bool IsDemoed(this ExternalCredentialType externalCredentialType) case ExternalCredentialType.OculusUseridNonce: return true; // These credential types are not demonstrated in the samples - case ExternalCredentialType.GoogleIdToken: case ExternalCredentialType.ItchioJwt: case ExternalCredentialType.ItchioKey: case ExternalCredentialType.EpicIdToken: diff --git a/Assets/StreamingAssets/EOS/EpicOnlineServicesConfig.json b/Assets/StreamingAssets/EOS/EpicOnlineServicesConfig.json index e01562cf8..39a77f32b 100644 --- a/Assets/StreamingAssets/EOS/EpicOnlineServicesConfig.json +++ b/Assets/StreamingAssets/EOS/EpicOnlineServicesConfig.json @@ -1 +1,26 @@ -{"productName":"EOS Plugin for Unity","productVersion":"1.0","productID":"f7102b835ed14b5fb6b3a05d87b3d101","sandboxID":"ab139ee5b644412781cf99f48b993b45","deploymentID":"c529498f660a4a3d8a123fd04552cb47","sandboxDeploymentOverrides":[],"isServer":false,"clientSecret":"aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms","clientID":"xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ","encryptionKey":"1111111111111111111111111111111111111111111111111111111111111111","platformOptionsFlags":[""],"authScopeOptionsFlags":[""],"tickBudgetInMilliseconds":0,"taskNetworkTimeoutSeconds":0.0,"ThreadAffinity_networkWork":"","ThreadAffinity_storageIO":"","ThreadAffinity_webSocketIO":"","ThreadAffinity_P2PIO":"","ThreadAffinity_HTTPRequestIO":"","ThreadAffinity_RTCIO":"","alwaysSendInputToOverlay":true,"initialButtonDelayForOverlay":"","repeatButtonDelayForOverlay":"","hackForceSendInputDirectlyToSDK":false,"toggleFriendsButtonCombination":["SpecialLeft"]} \ No newline at end of file +{ + "productName": "EOS Plugin for Unity", + "productVersion": "1.0", + "productID": "f7102b835ed14b5fb6b3a05d87b3d101", + "sandboxID": "ab139ee5b644412781cf99f48b993b45", + "deploymentID": "c529498f660a4a3d8a123fd04552cb47", + "sandboxDeploymentOverrides": [], + "isServer": false, + "clientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "clientID": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "encryptionKey": "1111111111111111111111111111111111111111111111111111111111111111", + "platformOptionsFlags": "None", + "authScopeOptionsFlags": "NoFlags", + "tickBudgetInMilliseconds": 0, + "taskNetworkTimeoutSeconds": 0.0, + "ThreadAffinity_networkWork": null, + "ThreadAffinity_storageIO": null, + "ThreadAffinity_webSocketIO": null, + "ThreadAffinity_P2PIO": null, + "ThreadAffinity_HTTPRequestIO": null, + "ThreadAffinity_RTCIO": null, + "alwaysSendInputToOverlay": true, + "initialButtonDelayForOverlay": null, + "repeatButtonDelayForOverlay": null, + "toggleFriendsButtonCombination": "SpecialLeft" +} diff --git a/Assets/StreamingAssets/EOS/eos_android_config.json b/Assets/StreamingAssets/EOS/eos_android_config.json index 65b1251e1..f54d87a17 100644 --- a/Assets/StreamingAssets/EOS/eos_android_config.json +++ b/Assets/StreamingAssets/EOS/eos_android_config.json @@ -1 +1,36 @@ -{"overrideValues":{"productName":"","productVersion":"","productID":"","sandboxID":"","deploymentID":"","sandboxDeploymentOverrides":[],"isServer":false,"clientSecret":"","clientID":"","encryptionKey":"","platformOptionsFlags":[""],"authScopeOptionsFlags":[],"tickBudgetInMilliseconds":0,"taskNetworkTimeoutSeconds":0.0,"ThreadAffinity_networkWork":"","ThreadAffinity_storageIO":"","ThreadAffinity_webSocketIO":"","ThreadAffinity_P2PIO":"","ThreadAffinity_HTTPRequestIO":"","ThreadAffinity_RTCIO":"","alwaysSendInputToOverlay":false,"initialButtonDelayForOverlay":"","repeatButtonDelayForOverlay":"","hackForceSendInputDirectlyToSDK":false,"toggleFriendsButtonCombination":["SpecialLeft"]},"flags":[""]} \ No newline at end of file +{ + "GoogleLoginClientID": null, + "GoogleLoginNonce": null, + "deployment": { + "SandboxId": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "DeploymentId": "c529498f660a4a3d8a123fd04552cb47" + }, + "clientCredentials": { + "ClientId": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "ClientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "EncryptionKey": "1111111111111111111111111111111111111111111111111111111111111111" + }, + "isServer": false, + "platformOptionsFlags": "None", + "authScopeOptionsFlags": "BasicProfile, FriendsList, Presence", + "integratedPlatformManagementFlags": "Disabled", + "tickBudgetInMilliseconds": 0, + "taskNetworkTimeoutSeconds": 0.0, + "threadAffinity": { + "NetworkWork": 0, + "StorageIo": 0, + "WebSocketIo": 0, + "P2PIo": 0, + "HttpRequestIo": 0, + "RTCIo": 0, + "EmbeddedOverlayMainThread": 0, + "EmbeddedOverlayWorkerThreads": 0 + }, + "alwaysSendInputToOverlay": false, + "initialButtonDelayForOverlay": 0.0, + "repeatButtonDelayForOverlay": 0.0, + "toggleFriendsButtonCombination": "None", + "schemaVersion": "1.0" +} diff --git a/Assets/StreamingAssets/EOS/eos_ios_config.json b/Assets/StreamingAssets/EOS/eos_ios_config.json index 50dc3e642..86ac96427 100644 --- a/Assets/StreamingAssets/EOS/eos_ios_config.json +++ b/Assets/StreamingAssets/EOS/eos_ios_config.json @@ -1 +1,34 @@ -{"overrideValues":{"productName":"","productVersion":"","productID":"","sandboxID":"","deploymentID":"","sandboxDeploymentOverrides":[],"isServer":false,"clientSecret":"","clientID":"","encryptionKey":"","platformOptionsFlags":[""],"authScopeOptionsFlags":[],"tickBudgetInMilliseconds":0,"taskNetworkTimeoutSeconds":0.0,"ThreadAffinity_networkWork":"","ThreadAffinity_storageIO":"","ThreadAffinity_webSocketIO":"","ThreadAffinity_P2PIO":"","ThreadAffinity_HTTPRequestIO":"","ThreadAffinity_RTCIO":"","alwaysSendInputToOverlay":false,"initialButtonDelayForOverlay":"","repeatButtonDelayForOverlay":"","hackForceSendInputDirectlyToSDK":false,"toggleFriendsButtonCombination":["SpecialLeft"]},"flags":[]} \ No newline at end of file +{ + "deployment": { + "SandboxId": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "DeploymentId": "c529498f660a4a3d8a123fd04552cb47" + }, + "clientCredentials": { + "ClientId": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "ClientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "EncryptionKey": "1111111111111111111111111111111111111111111111111111111111111111" + }, + "isServer": false, + "platformOptionsFlags": "None", + "authScopeOptionsFlags": "BasicProfile, FriendsList, Presence", + "integratedPlatformManagementFlags": "Disabled", + "tickBudgetInMilliseconds": 0, + "taskNetworkTimeoutSeconds": 0.0, + "threadAffinity": { + "NetworkWork": 0, + "StorageIo": 0, + "WebSocketIo": 0, + "P2PIo": 0, + "HttpRequestIo": 0, + "RTCIo": 0, + "EmbeddedOverlayMainThread": 0, + "EmbeddedOverlayWorkerThreads": 0 + }, + "alwaysSendInputToOverlay": false, + "initialButtonDelayForOverlay": 0.0, + "repeatButtonDelayForOverlay": 0.0, + "toggleFriendsButtonCombination": "None", + "schemaVersion": "1.0" +} diff --git a/Assets/StreamingAssets/EOS/eos_linux_config.json b/Assets/StreamingAssets/EOS/eos_linux_config.json index 50dc3e642..86ac96427 100644 --- a/Assets/StreamingAssets/EOS/eos_linux_config.json +++ b/Assets/StreamingAssets/EOS/eos_linux_config.json @@ -1 +1,34 @@ -{"overrideValues":{"productName":"","productVersion":"","productID":"","sandboxID":"","deploymentID":"","sandboxDeploymentOverrides":[],"isServer":false,"clientSecret":"","clientID":"","encryptionKey":"","platformOptionsFlags":[""],"authScopeOptionsFlags":[],"tickBudgetInMilliseconds":0,"taskNetworkTimeoutSeconds":0.0,"ThreadAffinity_networkWork":"","ThreadAffinity_storageIO":"","ThreadAffinity_webSocketIO":"","ThreadAffinity_P2PIO":"","ThreadAffinity_HTTPRequestIO":"","ThreadAffinity_RTCIO":"","alwaysSendInputToOverlay":false,"initialButtonDelayForOverlay":"","repeatButtonDelayForOverlay":"","hackForceSendInputDirectlyToSDK":false,"toggleFriendsButtonCombination":["SpecialLeft"]},"flags":[]} \ No newline at end of file +{ + "deployment": { + "SandboxId": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "DeploymentId": "c529498f660a4a3d8a123fd04552cb47" + }, + "clientCredentials": { + "ClientId": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "ClientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "EncryptionKey": "1111111111111111111111111111111111111111111111111111111111111111" + }, + "isServer": false, + "platformOptionsFlags": "None", + "authScopeOptionsFlags": "BasicProfile, FriendsList, Presence", + "integratedPlatformManagementFlags": "Disabled", + "tickBudgetInMilliseconds": 0, + "taskNetworkTimeoutSeconds": 0.0, + "threadAffinity": { + "NetworkWork": 0, + "StorageIo": 0, + "WebSocketIo": 0, + "P2PIo": 0, + "HttpRequestIo": 0, + "RTCIo": 0, + "EmbeddedOverlayMainThread": 0, + "EmbeddedOverlayWorkerThreads": 0 + }, + "alwaysSendInputToOverlay": false, + "initialButtonDelayForOverlay": 0.0, + "repeatButtonDelayForOverlay": 0.0, + "toggleFriendsButtonCombination": "None", + "schemaVersion": "1.0" +} diff --git a/Assets/StreamingAssets/EOS/eos_macos_config.json b/Assets/StreamingAssets/EOS/eos_macos_config.json index fcddcb6a5..c064a6f35 100644 --- a/Assets/StreamingAssets/EOS/eos_macos_config.json +++ b/Assets/StreamingAssets/EOS/eos_macos_config.json @@ -1,21 +1,25 @@ { - "flags": [], - "overrideValues": { - "productName": "", - "productVersion": "", - "productID": "", - "sandboxID": "", - "deploymentID": "", - "clientSecret": "", - "clientID": "", - "encryptionKey": "", - "platformOptionsFlags": [ - "" - ], - "tickBudgetInMilliseconds": 0, - "alwaysSendInputToOverlay": false, - "initialButtonDelayForOverlay": "", - "repeatButtonDelayForOverlay": "", - "hackForceSendInputDirectlyToSDK": false - } -} + "deployment": { + "SandboxId": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "DeploymentId": "c529498f660a4a3d8a123fd04552cb47" + }, + "clientCredentials": { + "ClientId": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "ClientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "EncryptionKey": "1111111111111111111111111111111111111111111111111111111111111111" + }, + "isServer": false, + "platformOptionsFlags": "None", + "authScopeOptionsFlags": "BasicProfile, FriendsList, Presence", + "integratedPlatformManagementFlags": "Disabled", + "tickBudgetInMilliseconds": 0, + "taskNetworkTimeoutSeconds": 0.0, + "threadAffinity": null, + "alwaysSendInputToOverlay": false, + "initialButtonDelayForOverlay": 0.0, + "repeatButtonDelayForOverlay": 0.0, + "toggleFriendsButtonCombination": "None", + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/Assets/StreamingAssets/EOS/eos_product_config.json b/Assets/StreamingAssets/EOS/eos_product_config.json new file mode 100644 index 000000000..e8e0af586 --- /dev/null +++ b/Assets/StreamingAssets/EOS/eos_product_config.json @@ -0,0 +1,38 @@ +{ + "ProductName": "EOS Plugin for Unity", + "ProductId": "f7102b835ed14b5fb6b3a05d87b3d101", + "ProductVersion": "1.0", + "imported": true, + "Clients": [ + { + "Value": { + "ClientId": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "ClientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "EncryptionKey": "1111111111111111111111111111111111111111111111111111111111111111" + }, + "Name": "Client" + } + ], + "Environments": { + "Deployments": [ + { + "Value": { + "SandboxId": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "DeploymentId": "c529498f660a4a3d8a123fd04552cb47" + }, + "Name": "Deployment" + } + ], + "Sandboxes": [ + { + "Value": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "Name": "Sandbox" + } + ] + }, + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/Assets/StreamingAssets/EOS/eos_steam_config.json.meta b/Assets/StreamingAssets/EOS/eos_product_config.json.meta similarity index 74% rename from Assets/StreamingAssets/EOS/eos_steam_config.json.meta rename to Assets/StreamingAssets/EOS/eos_product_config.json.meta index 8a7fa7053..c546b10c9 100644 --- a/Assets/StreamingAssets/EOS/eos_steam_config.json.meta +++ b/Assets/StreamingAssets/EOS/eos_product_config.json.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f02c2231c8b0a0343b93b2dc972813e6 +guid: 6f61bb7930941144fbd41337518cbfe8 DefaultImporter: externalObjects: {} userData: diff --git a/Assets/StreamingAssets/EOS/eos_steam_config.json b/Assets/StreamingAssets/EOS/eos_steam_config.json deleted file mode 100644 index e1bed88f0..000000000 --- a/Assets/StreamingAssets/EOS/eos_steam_config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "flags": [], - "overrideLibraryPath": "", - "steamSDKMajorVersion": 0, - "steamSDKMinorVersion": 0 -} \ No newline at end of file diff --git a/Assets/StreamingAssets/EOS/eos_windows_config.json b/Assets/StreamingAssets/EOS/eos_windows_config.json new file mode 100644 index 000000000..eff0476a8 --- /dev/null +++ b/Assets/StreamingAssets/EOS/eos_windows_config.json @@ -0,0 +1,34 @@ +{ + "deployment": { + "SandboxId": { + "Value": "ab139ee5b644412781cf99f48b993b45" + }, + "DeploymentId": "c529498f660a4a3d8a123fd04552cb47" + }, + "clientCredentials": { + "ClientId": "xyza7891wPzGRvRf4SkjlIF8YuqlRLbQ", + "ClientSecret": "aXPlP1xDH0PXnp5U+i+M5pYHhaE1a8viV0l1GO422ms", + "EncryptionKey": "1111111111111111111111111111111111111111111111111111111111111111" + }, + "isServer": false, + "platformOptionsFlags": "None", + "authScopeOptionsFlags": "BasicProfile, FriendsList, Presence", + "integratedPlatformManagementFlags": "Disabled", + "tickBudgetInMilliseconds": 0, + "taskNetworkTimeoutSeconds": 0.0, + "threadAffinity": { + "NetworkWork": 0, + "StorageIo": 0, + "WebSocketIo": 0, + "P2PIo": 0, + "HttpRequestIo": 0, + "RTCIo": 0, + "EmbeddedOverlayMainThread": 0, + "EmbeddedOverlayWorkerThreads": 0 + }, + "alwaysSendInputToOverlay": true, + "initialButtonDelayForOverlay": 0.0, + "repeatButtonDelayForOverlay": 0.0, + "toggleFriendsButtonCombination": "SpecialLeft", + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/Assets/StreamingAssets/EOS/eos_windows_config.json.meta b/Assets/StreamingAssets/EOS/eos_windows_config.json.meta new file mode 100644 index 000000000..fbd8e21e9 --- /dev/null +++ b/Assets/StreamingAssets/EOS/eos_windows_config.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0a4ae0b266f5fa6438b55c2bd404ce62 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Common/EOSTestBase.cs b/Assets/Tests/Common/EOSTestBase.cs index 5e417a64a..8a8719d52 100644 --- a/Assets/Tests/Common/EOSTestBase.cs +++ b/Assets/Tests/Common/EOSTestBase.cs @@ -33,7 +33,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests /// /// Initial setup and shutdown procedure for all tests. /// - public class EOSTestBase + public partial class EOSTestBase { /// /// Common constants used in the tests. diff --git a/Assets/Tests/Editor/UnitTestResultsWindow.cs b/Assets/Tests/Editor/UnitTestResultsWindow.cs index b8cdf65a8..605c2739f 100644 --- a/Assets/Tests/Editor/UnitTestResultsWindow.cs +++ b/Assets/Tests/Editor/UnitTestResultsWindow.cs @@ -71,7 +71,7 @@ private class TestResultData // NOTE: This is currently disabled as it is being investigated whether // there is a more built-in manner in which to inspect unit test // results. - // [MenuItem("Tools/EOS Plugin/Tests/Result Parser", priority = 11)] + // [MenuItem("EOS Plugin/Tests/Result Parser", priority = 11)] public static void OpenUnitTestResultsWindow() { var window = GetWindow(); diff --git a/Assets/Tests/PlayMode/AuthenticationTests.cs b/Assets/Tests/PlayMode/AuthenticationTests.cs index c19af1a5c..d264ca5f7 100644 --- a/Assets/Tests/PlayMode/AuthenticationTests.cs +++ b/Assets/Tests/PlayMode/AuthenticationTests.cs @@ -31,7 +31,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Auth using UnityEngine; using UnityEngine.TestTools; - public class AuthenticationTests + public partial class AuthenticationTests { GameObject eosObject; protected const float LoginTestTimeout = 30f; diff --git a/Assets/Tests/PlayMode/Config/EOSConfigTests.cs b/Assets/Tests/PlayMode/Config/EOSConfigTests.cs new file mode 100644 index 000000000..256d71e4f --- /dev/null +++ b/Assets/Tests/PlayMode/Config/EOSConfigTests.cs @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices.Tests.Config +{ + using Epic.OnlineServices.Platform; + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using static PlayEveryWare.EpicOnlineServices.EOSConfig; + using Config = EpicOnlineServices.Config; + + public class EOSConfigTests + { + [Test] + public void ProductName_MustNotBeEmpty() + { + EOSConfig config = EOSConfig.Get(); + config.productName = string.Empty; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + Assert.Fail($"Config should have failing attributes."); + } + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(EOSConfig.productName), + failingAttributes, + NonEmptyStringFieldValidatorAttribute.FieldIsEmptyMessage), + "There should be a failure of the expected type and message."); + } + + [Test] + public void ProductName_SuccessfulParsing() + { + EOSConfig config = EOSConfig.Get(); + config.productName = "My Valid Product Name"; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(EOSConfig.productName), + failingAttributes, + NonEmptyStringFieldValidatorAttribute.FieldIsEmptyMessage), + "Product Name should not have errors describing it as an empty field."); + } + + [Test] + public void ProductVersion_MustNotBeEmpty() + { + EOSConfig config = EOSConfig.Get(); + config.productVersion = string.Empty; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + Assert.Fail($"Config should have failing attributes."); + } + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(EOSConfig.productVersion), + failingAttributes, + NonEmptyStringFieldValidatorAttribute.FieldIsEmptyMessage), + "There should be a failure of the expected type and message."); + } + + [Test] + public void ProductVersion_SuccessfulParsing() + { + EOSConfig config = EOSConfig.Get(); + config.productVersion = "123.456"; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(EOSConfig.productVersion), + failingAttributes, + NonEmptyStringFieldValidatorAttribute.FieldIsEmptyMessage), + "Product Version should not have errors describing it as an empty field."); + } + + [Test] + public void ProductId_MustBeValidGUID() + { + EOSConfig config = EOSConfig.Get(); + config.productID = "notaguid"; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + Assert.Fail($"Config should have failing attributes."); + } + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(EOSConfig.productID), + failingAttributes, + GUIDFieldValidatorAttribute.NotAGuidMessage), + "There should be a failure of the expected type and message."); + } + + [Test] + public void ProductId_SuccessfulParsing() + { + EOSConfig config = EOSConfig.Get(); + config.productID = Guid.NewGuid().ToString(); + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(EOSConfig.productID), + failingAttributes, + GUIDFieldValidatorAttribute.NotAGuidMessage), + "Product Id should not have errors describing it as an invalid GUID."); + } + + [Test] + public void SandboxID_MustBeValidGUID_OrHavePrefix() + { + EOSConfig config = EOSConfig.Get(); + config.sandboxID = "notaguid"; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + Assert.Fail($"Config should have failing attributes."); + } + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(EOSConfig.sandboxID), + failingAttributes, + SandboxIDFieldValidatorAttribute.FieldDidNotMatchMessage), + "There should be a failure of the expected type and message."); + } + + [Test] + public void SandboxID_SuccessfulParsing_WithGUID() + { + EOSConfig config = EOSConfig.Get(); + config.sandboxID = Guid.NewGuid().ToString(); + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(EOSConfig.sandboxID), + failingAttributes, + SandboxIDFieldValidatorAttribute.FieldDidNotMatchMessage), + "Sandbox Id should not have errors relating to it failing to parse."); + } + + [Test] + public void SandboxID_SuccessfulParsing_WithDevelopmentEnvironment() + { + EOSConfig config = EOSConfig.Get(); + config.sandboxID = "p-1234567890ABCDEFGHIJKLMNOPQRST"; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(EOSConfig.sandboxID), + failingAttributes, + SandboxIDFieldValidatorAttribute.FieldDidNotMatchMessage), + "Sandbox Id should not have errors relating to it failing to parse."); + } + + [Test] + public void DeploymentOverride_MustBeValid() + { + EOSConfig config = EOSConfig.Get(); + config.sandboxDeploymentOverrides = new List() + { + new SandboxDeploymentOverride() + { + sandboxID = "abc", + deploymentID = "abc" + } + }; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + Assert.Fail($"Config should have failing attributes."); + } + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(SandboxDeploymentOverride.deploymentID), + failingAttributes, + GUIDFieldValidatorAttribute.NotAGuidMessage), + "Deployment ID in sandbox override should have failure relating to it."); + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(SandboxDeploymentOverride.sandboxID), + failingAttributes, + SandboxIDFieldValidatorAttribute.FieldDidNotMatchMessage), + "Sandbox ID in sandbox override should have failure relating to it."); + } + + [Test] + public void DeploymentOverride_SuccessfulParse() + { + EOSConfig config = EOSConfig.Get(); + config.sandboxDeploymentOverrides = new List() + { + new SandboxDeploymentOverride() + { + sandboxID = Guid.NewGuid().ToString(), + deploymentID = Guid.NewGuid().ToString() + } + }; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(SandboxDeploymentOverride.deploymentID), + failingAttributes, + GUIDFieldValidatorAttribute.NotAGuidMessage), + "Deployment ID in sandbox override should not have error relating to GUID failing to parse."); + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(SandboxDeploymentOverride.sandboxID), + failingAttributes, + SandboxIDFieldValidatorAttribute.FieldDidNotMatchMessage), + "Sandbox ID in sandbox override should not have error relating to it failing to parse."); + } + + [Test] + public void DeploymentID_MustBeValidGUID() + { + EOSConfig config = EOSConfig.Get(); + config.deploymentID = "notaguid"; + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + Assert.Fail($"Config should have failing attributes."); + } + + Assert.IsTrue(failuresIncludeExpectedFailure( + nameof(EOSConfig.deploymentID), + failingAttributes, + GUIDFieldValidatorAttribute.NotAGuidMessage), + "There should be a failure of the expected type and message."); + } + + [Test] + public void DeploymentID_SuccessfulParse() + { + EOSConfig config = EOSConfig.Get(); + config.deploymentID = Guid.NewGuid().ToString(); + + if (!FieldValidator.TryGetFailingValidatorAttributes(config, out List failingAttributes)) + { + // If there are no errors, then this test is a success + // The config might be failing in other ways + return; + } + + Assert.IsFalse(failuresIncludeExpectedFailure( + nameof(EOSConfig.deploymentID), + failingAttributes, + GUIDFieldValidatorAttribute.NotAGuidMessage), + "There should be a failure of the expected type and message."); + } + + /// + /// Determines if the provided list of failures contains an expected failure + /// + /// The kind of validator attribute that is expected. + /// The name of the field that is being checked for. + /// A list of all failures from validation. + /// + /// A specific error message to check for. + /// Optional. If null or empty, this is not used. + /// + /// True if the expected failure is within the list. + private bool failuresIncludeExpectedFailure(string fieldName, List failures, string message = "") where T : FieldValidatorAttribute + { + foreach (FieldValidatorFailure currentFailure in failures) + { + if (currentFailure.FailingAttribute is not T) + { + continue; + } + + if (currentFailure.FieldInfo.Name != fieldName) + { + continue; + } + + if (!string.IsNullOrEmpty(message)) + { + if (currentFailure.FailingMessage != message) + { + continue; + } + } + + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Platforms/Linux/LinuxConfigEditor.cs.meta b/Assets/Tests/PlayMode/Config/EOSConfigTests.cs.meta similarity index 83% rename from Assets/Plugins/Source/Editor/Platforms/Linux/LinuxConfigEditor.cs.meta rename to Assets/Tests/PlayMode/Config/EOSConfigTests.cs.meta index c603f0155..6e887fac5 100644 --- a/Assets/Plugins/Source/Editor/Platforms/Linux/LinuxConfigEditor.cs.meta +++ b/Assets/Tests/PlayMode/Config/EOSConfigTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 15af44e19a8444e43be1da0094b9627c +guid: aba182ef6b57a5e4ea966fed550cbb8a MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Tests/PlayMode/Config/JsonConverters.meta b/Assets/Tests/PlayMode/Config/JsonConverters.meta new file mode 100644 index 000000000..f446ba9a4 --- /dev/null +++ b/Assets/Tests/PlayMode/Config/JsonConverters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: efca3f0e67de2384a993a8d1be6f9d4a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/Config/JsonConverters/ListOfStringsToEnumConverterTests.cs b/Assets/Tests/PlayMode/Config/JsonConverters/ListOfStringsToEnumConverterTests.cs new file mode 100644 index 000000000..ceba760bf --- /dev/null +++ b/Assets/Tests/PlayMode/Config/JsonConverters/ListOfStringsToEnumConverterTests.cs @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices.Tests.Config +{ + using Epic.OnlineServices.UI; + using NUnit.Framework; + using Newtonsoft.Json; + using System; + using System.Collections.Generic; + using Newtonsoft.Json.Linq; + public static partial class ListOfStringsToEnumConverterTests + { + private static readonly Dictionary CUSTOM_MAPPING = new() + { + { "FirstLetter", TestOrderEnum.A }, + { "SecondLetter", TestOrderEnum.B }, + { "ThirdLetter", TestOrderEnum.C }, + { "AppleStartsWith", TestOrderEnum.A } + }; + + [Flags] + private enum TestOrderEnum : int + { + None = 0x00000, + A = 0x00001, + B = 0x00004, + C = 0x00008 + } + + private class ListOfStringsToEnumConverterTestClass : ListOfStringsToEnumConverter + { + protected override TestOrderEnum FromStringArray(JArray array) + { + return FromStringArrayWithCustomMapping(array, CUSTOM_MAPPING); + } + } + + [Test] + public static void ListOfStringsToEnumConverterTestClass_ReadJson_ArrayOfStrings_ReturnsCorrectEnum() + { + var converter = new ListOfStringsToEnumConverterTestClass(); + var json = JArray.FromObject(new[] { "FirstLetter", "ThirdLetter", "B" }); + var result = (TestOrderEnum)converter.ReadJson(json.CreateReader(), typeof(InputStateButtonFlags), null, null); + + Assert.AreEqual(TestOrderEnum.A | TestOrderEnum.C | TestOrderEnum.B, result); + } + + [Test] + public static void ListOfStringsToEnumConverterTestClass_ReadJson_SingleString_ReturnsCorrectEnum() + { + var converter = new ListOfStringsToEnumConverterTestClass(); + var json = JToken.FromObject("A"); + var result = (TestOrderEnum)converter.ReadJson(json.CreateReader(), typeof(InputStateButtonFlags), null, null); + + Assert.AreEqual(TestOrderEnum.A, result); + } + + [Test] + public static void ListOfStringsToEnumConverterTestClass_ReadJson_Null_ReturnsDefaultEnum() + { + var converter = new ListOfStringsToEnumConverterTestClass(); + var result = (TestOrderEnum)converter.ReadJson(new JValue((string)null).CreateReader(), typeof(InputStateButtonFlags), null, null); + + Assert.AreEqual(TestOrderEnum.None, result); + } + + [Test] + public static void ListOfStringsToEnumConverterTestClass_ReadJson_InvalidTokenType_ThrowsException() + { + var converter = new ListOfStringsToEnumConverterTestClass(); + var invalidJson = JToken.FromObject(123); // Invalid type for enum parsing + + Assert.Throws(() => converter.ReadJson(invalidJson.CreateReader(), typeof(TestOrderEnum), null, null)); + } + + } +} \ No newline at end of file diff --git a/Assets/Tests/PlayMode/Config/JsonConverters/ListOfStringsToEnumConverterTests.cs.meta b/Assets/Tests/PlayMode/Config/JsonConverters/ListOfStringsToEnumConverterTests.cs.meta new file mode 100644 index 000000000..2f071f4e4 --- /dev/null +++ b/Assets/Tests/PlayMode/Config/JsonConverters/ListOfStringsToEnumConverterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff43290b33c257f468c0797e66078a70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/Config/JsonConverters/StringToTypeConverterTests.cs b/Assets/Tests/PlayMode/Config/JsonConverters/StringToTypeConverterTests.cs new file mode 100644 index 000000000..cbb156eb8 --- /dev/null +++ b/Assets/Tests/PlayMode/Config/JsonConverters/StringToTypeConverterTests.cs @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices.Tests.Config +{ + using NUnit.Framework; + using Newtonsoft.Json; + using System; + + public static partial class StringToTypeConverterTests + { + private enum TestEnum + { + None, + First, + Second + } + + private class TestClass + { + [JsonConverter(typeof(StringToTypeConverter))] + public ulong ULongValue { get; set; } + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? NullableULongValue { get; set; } + + [JsonConverter(typeof(StringToTypeConverter))] + public Guid GuidValue { get; set; } + + [JsonConverter(typeof(StringToTypeConverter))] + public Guid? NullableGuidValue { get; set; } + + [JsonConverter(typeof(StringToTypeConverter))] + public TestEnum EnumValue { get; set; } + } + + [TestFixture] + internal partial class StringToTypeConverterUnitTests + { + [Test] + public void Deserialize_ULong_FromString() + { + const string json = "{ 'ULongValue': '1234567890' }"; + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(1234567890UL, obj.ULongValue); + } + + [Test] + public void Deserialize_NullableULong_FromString() + { + const string json = "{ 'NullableULongValue': '9876543210' }"; + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(9876543210UL, obj.NullableULongValue); + } + + [Test] + public void Deserialize_NullableULong_FromNull() + { + const string json = "{ 'NullableULongValue': null }"; + var obj = JsonConvert.DeserializeObject(json); + Assert.IsNull(obj.NullableULongValue); + } + + [Test] + public void Deserialize_Guid_FromString() + { + const string guidString = "550e8400-e29b-41d4-a716-446655440000"; + string json = $"{{ 'GuidValue': '{guidString}' }}"; + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(Guid.Parse(guidString), obj.GuidValue); + } + + [Test] + public void Deserialize_NullableGuid_FromString() + { + const string guidString = "d2719a31-2b76-4b47-bb39-d5ac8b8a5c1f"; + string json = $"{{ 'NullableGuidValue': '{guidString}' }}"; + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(Guid.Parse(guidString), obj.NullableGuidValue); + } + + [Test] + public void Deserialize_NullableGuid_FromNull() + { + const string json = "{ 'NullableGuidValue': null }"; + var obj = JsonConvert.DeserializeObject(json); + Assert.IsNull(obj.NullableGuidValue); + } + + [Test] + public void Deserialize_Enum_FromString() + { + const string json = "{ 'EnumValue': 'Second' }"; + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(TestEnum.Second, obj.EnumValue); + } + + [Test] + public void Deserialize_InvalidULong_ThrowsException() + { + const string json = "{ 'ULongValue': 'invalid_number' }"; + Assert.Throws(() => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void Deserialize_InvalidGuid_ThrowsException() + { + const string json = "{ 'GuidValue': 'not_a_guid' }"; + Assert.Throws(() => JsonConvert.DeserializeObject(json)); + } + + [Test] + public void Serialize_ULongValue() + { + var obj = new TestClass { ULongValue = 1234567890UL }; + string json = JsonConvert.SerializeObject(obj); + StringAssert.Contains(@"""ULongValue"":1234567890", json); + } + + [Test] + public void Serialize_NullableULongValue_Null() + { + var obj = new TestClass { NullableULongValue = null }; + string json = JsonConvert.SerializeObject(obj); + StringAssert.Contains(@"""NullableULongValue"":null", json); + } + + [Test] + public void Serialize_GuidValue() + { + var guid = Guid.NewGuid(); + var obj = new TestClass { GuidValue = guid }; + string json = JsonConvert.SerializeObject(obj); + StringAssert.Contains($@"""GuidValue"":""{guid}""", json); + } + + [Test] + public void Serialize_EnumValue() + { + var obj = new TestClass { EnumValue = TestEnum.First }; + string json = JsonConvert.SerializeObject(obj); + StringAssert.Contains(@"""EnumValue"":1", json); + } + + [Test] + public void Deserialize_ULong_FromNumber() + { + const string json = "{ 'ULongValue': 1234567890 }"; + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(1234567890UL, obj.ULongValue); + } + + [Test] + public void Deserialize_Enum_FromNumber() + { + const string json = "{ 'EnumValue': 2 }"; // Second = 2 + var obj = JsonConvert.DeserializeObject(json); + Assert.AreEqual(TestEnum.Second, obj.EnumValue); + } + } + } +} \ No newline at end of file diff --git a/Assets/Tests/PlayMode/Config/JsonConverters/StringToTypeConverterTests.cs.meta b/Assets/Tests/PlayMode/Config/JsonConverters/StringToTypeConverterTests.cs.meta new file mode 100644 index 000000000..5844d9072 --- /dev/null +++ b/Assets/Tests/PlayMode/Config/JsonConverters/StringToTypeConverterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f00b460745c63541aa4f529ad4894be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/ConnectTests.cs b/Assets/Tests/PlayMode/ConnectTests.cs index e366740f2..13bd4ef4a 100644 --- a/Assets/Tests/PlayMode/ConnectTests.cs +++ b/Assets/Tests/PlayMode/ConnectTests.cs @@ -30,7 +30,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Connect using UnityEngine; using UnityEngine.TestTools; - public class ConnectTests + public partial class ConnectTests { GameObject eosObject; protected const float LoginTestTimeout = 30f; diff --git a/Assets/Tests/PlayMode/Extensions/FileInfoExtensionsTests.cs b/Assets/Tests/PlayMode/Extensions/FileInfoExtensionsTests.cs index 0a6b733a6..0ad69bcc7 100644 --- a/Assets/Tests/PlayMode/Extensions/FileInfoExtensionsTests.cs +++ b/Assets/Tests/PlayMode/Extensions/FileInfoExtensionsTests.cs @@ -22,10 +22,10 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Extensions { + using Common.Extensions; using NUnit.Framework; using System; using System.IO; - using PlayEveryWare.EpicOnlineServices.Extensions; using System.Collections.Generic; [TestFixture] diff --git a/Assets/Tests/PlayMode/Extensions/ListExtensionsTests.cs b/Assets/Tests/PlayMode/Extensions/ListExtensionsTests.cs index 1a8db486e..1f644b029 100644 --- a/Assets/Tests/PlayMode/Extensions/ListExtensionsTests.cs +++ b/Assets/Tests/PlayMode/Extensions/ListExtensionsTests.cs @@ -22,8 +22,8 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Extensions { + using Common.Extensions; using NUnit.Framework; - using PlayEveryWare.EpicOnlineServices.Extensions; using System.Collections.Generic; using System.Linq; diff --git a/Assets/Tests/PlayMode/Services/AchievementsTests.cs b/Assets/Tests/PlayMode/Services/AchievementsTests.cs index e052e2406..9e13b8a98 100644 --- a/Assets/Tests/PlayMode/Services/AchievementsTests.cs +++ b/Assets/Tests/PlayMode/Services/AchievementsTests.cs @@ -33,7 +33,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Achievements /// /// Integration tests for achievements. /// - public class AchievementsTests : EOSTestBase + public partial class AchievementsTests : EOSTestBase { /// /// Checks to see if we can get a count for the number of achievements for the product user. diff --git a/Assets/Tests/PlayMode/Services/ClientLobbyTests.cs b/Assets/Tests/PlayMode/Services/ClientLobbyTests.cs index 628da6099..04fab7a11 100644 --- a/Assets/Tests/PlayMode/Services/ClientLobbyTests.cs +++ b/Assets/Tests/PlayMode/Services/ClientLobbyTests.cs @@ -34,7 +34,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Lobby /// /// Lobby connection tests that test connecting to an existing lobby. /// - public class ClientLobbyTests : EOSTestBase + public partial class ClientLobbyTests : EOSTestBase { private string lobbyId; private NotifyEventHandle lobbyInviteNotification; diff --git a/Assets/Tests/PlayMode/Services/ClientSessionTests.cs b/Assets/Tests/PlayMode/Services/ClientSessionTests.cs index 8e0107170..0592a4b48 100644 --- a/Assets/Tests/PlayMode/Services/ClientSessionTests.cs +++ b/Assets/Tests/PlayMode/Services/ClientSessionTests.cs @@ -35,7 +35,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Sessions /// /// Session connection tests that test connecting to an existing session. /// - public class ClientSessionTests : EOSTestBase + public partial class ClientSessionTests : EOSTestBase { private const ulong InvalidNotificationId = 0; diff --git a/Assets/Tests/PlayMode/Services/EOSSessionsManagerTests.cs b/Assets/Tests/PlayMode/Services/EOSSessionsManagerTests.cs index f1e100fb4..7660c6e86 100644 --- a/Assets/Tests/PlayMode/Services/EOSSessionsManagerTests.cs +++ b/Assets/Tests/PlayMode/Services/EOSSessionsManagerTests.cs @@ -34,7 +34,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Sessions using System.Collections.Generic; using System.Text.RegularExpressions; - public class EOSSessionsManagerTests : EOSTestBase + public partial class EOSSessionsManagerTests : EOSTestBase { protected EOSSessionsManager ManagerInstance { get; set; } = null; diff --git a/Assets/Tests/PlayMode/Services/FriendsTests.cs b/Assets/Tests/PlayMode/Services/FriendsTests.cs index bc11516c8..d9478a8bf 100644 --- a/Assets/Tests/PlayMode/Services/FriendsTests.cs +++ b/Assets/Tests/PlayMode/Services/FriendsTests.cs @@ -39,7 +39,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Friends /// /// Integration tests for friend related calls. /// - public class FriendsTests : EOSTestBase + public partial class FriendsTests : EOSTestBase { private FriendsInterface _friendsInterface; private UserInfoInterface _userInfoInterface; diff --git a/Assets/Tests/PlayMode/Services/LeaderboardTests.cs b/Assets/Tests/PlayMode/Services/LeaderboardTests.cs index 6f1e69e8a..be0a7a628 100644 --- a/Assets/Tests/PlayMode/Services/LeaderboardTests.cs +++ b/Assets/Tests/PlayMode/Services/LeaderboardTests.cs @@ -35,7 +35,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Leaderboard /// /// Integration tests for leaderboard related calls. /// - public class LeaderboardTests : EOSTestBase + public partial class LeaderboardTests : EOSTestBase { private LeaderboardsInterface _leaderboardsInterface; private StatsInterface statsInterface; diff --git a/Assets/Tests/PlayMode/Services/LobbyTests.cs b/Assets/Tests/PlayMode/Services/LobbyTests.cs index 13cd78970..32dfc9767 100644 --- a/Assets/Tests/PlayMode/Services/LobbyTests.cs +++ b/Assets/Tests/PlayMode/Services/LobbyTests.cs @@ -34,7 +34,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Lobby /// /// Lobby related tests. /// - public class LobbyTests : EOSTestBase + public partial class LobbyTests : EOSTestBase { private CreateLobbyCallbackInfo? createLobbyResult = null; private LobbySearchFindCallbackInfo? searchLobbyResult = null; diff --git a/Assets/Tests/PlayMode/Services/PlayerDataStorageServiceTests.cs b/Assets/Tests/PlayMode/Services/PlayerDataStorageServiceTests.cs index d808ea319..1090e2b49 100644 --- a/Assets/Tests/PlayMode/Services/PlayerDataStorageServiceTests.cs +++ b/Assets/Tests/PlayMode/Services/PlayerDataStorageServiceTests.cs @@ -30,7 +30,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.IntegrationTests using System.Text; using System.Collections.Generic; - public class PlayerDataStorageServiceTests + public partial class PlayerDataStorageServiceTests : EOSTestBase { [UnitySetUp] diff --git a/Assets/Tests/PlayMode/Services/PlayerStorageTests.cs b/Assets/Tests/PlayMode/Services/PlayerStorageTests.cs index 3521433de..c02afc4f0 100644 --- a/Assets/Tests/PlayMode/Services/PlayerStorageTests.cs +++ b/Assets/Tests/PlayMode/Services/PlayerStorageTests.cs @@ -36,7 +36,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.PlayerStorage /// /// Tests for the player storage functionality of EOS. /// - public class PlayerStorageTests : EOSTestBase + public partial class PlayerStorageTests : EOSTestBase { private const uint MAX_CHUNK_SIZE = 4096; private static readonly System.Random random = new(); diff --git a/Assets/Tests/PlayMode/Services/SessionTests.cs b/Assets/Tests/PlayMode/Services/SessionTests.cs index ba9a74fb8..ede99716c 100644 --- a/Assets/Tests/PlayMode/Services/SessionTests.cs +++ b/Assets/Tests/PlayMode/Services/SessionTests.cs @@ -33,7 +33,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.Sessions /// /// Integration tests for sessions and matchmaking. /// - public class SessionTests : EOSTestBase + public partial class SessionTests : EOSTestBase { private const string SessionName = "IntegrationTestSession"; private const string LevelName = "CASTLE"; diff --git a/Assets/Tests/PlayMode/Services/TitleStorageTests.cs b/Assets/Tests/PlayMode/Services/TitleStorageTests.cs index cd88034e7..db5e56799 100644 --- a/Assets/Tests/PlayMode/Services/TitleStorageTests.cs +++ b/Assets/Tests/PlayMode/Services/TitleStorageTests.cs @@ -33,7 +33,7 @@ namespace PlayEveryWare.EpicOnlineServices.Tests.Services.TitleStorage /// /// Tests for the title storage functionality of EOS. /// - public class TitleStorageTests : EOSTestBase + public partial class TitleStorageTests : EOSTestBase { private TitleStorageInterface _titleStorageHandle; diff --git a/Assets/Tests/PlayMode/com.playeveryware.eos.tests.playmode.asmdef b/Assets/Tests/PlayMode/com.playeveryware.eos.tests.playmode.asmdef index f5cf98cc5..6a41fad26 100644 --- a/Assets/Tests/PlayMode/com.playeveryware.eos.tests.playmode.asmdef +++ b/Assets/Tests/PlayMode/com.playeveryware.eos.tests.playmode.asmdef @@ -15,7 +15,8 @@ "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": [ - "nunit.framework.dll" + "nunit.framework.dll", + "Newtonsoft.Json.dll" ], "autoReferenced": false, "defineConstraints": [ diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index eb5b9b397..e14a3b16d 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -5,7 +5,7 @@ "depth": 0, "source": "local", "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.editorcoroutines": "1.0.0" } }, @@ -88,8 +88,8 @@ "depth": 0, "source": "registry", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.2.0" + "com.unity.transport": "1.2.0", + "com.unity.nuget.mono-cecil": "1.10.1" }, "url": "https://packages.unity.com" }, @@ -102,7 +102,7 @@ }, "com.unity.nuget.newtonsoft-json": { "version": "3.0.2", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -121,9 +121,9 @@ "depth": 1, "source": "registry", "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.modules.androidjni": "1.0.0" + "com.unity.modules.unitywebrequest": "1.0.0" }, "url": "https://packages.unity.com" }, @@ -176,9 +176,9 @@ "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" @@ -198,8 +198,8 @@ "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "1.2.4", "com.unity.burst": "1.6.6", + "com.unity.collections": "1.2.4", "com.unity.mathematics": "1.2.6" }, "url": "https://packages.unity.com" diff --git a/com.playeveryware.eos/CHANGELOG.md b/com.playeveryware.eos/CHANGELOG.md index 56a397f94..13ecbe150 100644 --- a/com.playeveryware.eos/CHANGELOG.md +++ b/com.playeveryware.eos/CHANGELOG.md @@ -2,6 +2,66 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +<<<<<<< HEAD +## [3.3.4] - 2024-10-28 + +### Added +- **Google ID Login Support:** Added the ability to log in using Google ID via the Connect interface. This enhances user authentication options. [Commit: 85d2229e] +- **SDK Integration Update to 1.16.4:** + - Integrated SDK version 1.16.4, updating both managed and native code. + - Included new Windows x86 and macOS native binaries for broader platform support. +- **Authentication Listener Enhancements:** + - The Authentication Listener now differentiates between Connect and Auth logins, providing more detailed login change events. [Commit: 5f266df0] +- **Sample Project Improvements:** + - **Selectable State Handler:** Introduced a new handler to improve UI interaction logic within samples. [Commit: 3f300046] + - **Tooltips for Player Data Storage:** Added tooltips to enhance usability in the Player Data Storage sample. [Commit: 9250ecc9] +- **File System Utility Additions:** + - Added synchronous functions to `FileSystemUtility` that do not rely on asynchronous counterparts, improving performance in certain contexts. [Commit: 40308f2f] +- **Switch to Newtonsoft Json Library:** + - Replaced Unity's `JsonUtility` with `Newtonsoft.Json` for better JSON serialization/deserialization. [Commit: 2c6d3e5b] +- **Utility Scripts:** + - **Commit Message Printer:** Added a script to print commit messages between the last tag and the current branch. [Commit: 0b715d13] + - **Documentation Link Checker:** Introduced a Python script to validate links in markdown files, improving documentation reliability. + +### Fixed +- **Platform Config Writing:** Fixed issues with saving configurations in `PlatformConfig`, ensuring settings are correctly written. [Commit: e2f0cc91] +- **SandboxId Null Handling:** Resolved an issue where SandboxId could not be set to null, improving configuration flexibility. [Commit: e056cf16] +- **Compile Conditionals:** Corrected compile conditionals for config value writing, ensuring proper behavior across different builds. [Commit: eb857c90] +- **Refresh Token Login:** Fixed the refresh token login process to function as expected. [Commit: 31e912db] +- **Event Unsubscription:** + - Ensured file list updates are unsubscribed after scene changes to prevent memory leaks. [Commit: 3e3c7c28] + - Unsubscribed listeners properly upon leaving scenes in samples. [Commit: 5fa65c34] +- **`EOS_DISABLE` Support:** Adjusted scripts and build processes to respect the `EOS_DISABLE` define, preventing unnecessary executions when the plugin is disabled. [Commit: b7069409] +- **Native Code Parsing Consistency:** Aligned native code parsing with managed code to maintain consistency and backward compatibility. [Commit: bcb17407] +- **Configuration Field Fixes:** + - Corrected default values and types for various configuration fields. + - Fixed issues with enum value conversions in configuration fields. +- **iOSBuilder Correction:** Fixed `IOSBuilder` to use the correct `FileSystemUtility` class, ensuring proper file operations on iOS. [Commit: 2dbcf873] +- **Android IO Operations:** Resolved input/output issues on Android devices, ensuring configurations are read and written correctly. [Commit: 1e5c3595] +- **Version Update in Package Info:** Updated the version number in `EOSPackageInfo` to reflect the new release. [Commit: f899cfb1] + +### Changed +- **User Interface Enhancements:** + - Added tooltips to Title Storage and `ProductionEnvironments` fields for better clarity. [Commits: bf212362, 104092af] +Improved the configuration interface with explicit priorities and reorganized advanced operations for better user experience. [Commits: 1385c613, 8e1af06f] +- **Configuration System Overhaul:** + - Separated product configuration from platform configuration for cleaner setup management. [Commit: 0a2bd1fe] + - Continued development on the configuration override system for more flexible configuration handling. +- **GUI Editor Refinements:** Made several improvements to `GUIEditorUtility` to enhance the editor experience for developers. +- **Sample Code Refactoring:** + - Renamed and refactored sample classes and methods for clarity and better code organization. [Commits: ecc87561, e8497eda, 4026907a] + - Adjusted `SelectableStateHandler` to improve logic handling within the manager. [Commit: 568ec64e] +- **Documentation Updates:** + - Added a comprehensive "Full Guide" and made several grammar and spelling corrections. + - Fixed broken links and updated references to improve documentation accuracy. + - Updated line coverage statistics to reflect current test coverage. +- **Code Cleanup:** + - Removed unnecessary using statements and unreferenced functions. + - Restored consistent formatting across various files. + - Removed temporary files and logging statements used during debugging. +### Removals and Deprecations +- **Obsolete Config Editors:** Removed outdated configuration editors that are no longer in use. [Commit: a2fdd306] +======= ## [3.3.4] - 2024-11-26 ### Changed @@ -17,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: Add isolated changes that address the Android thread IO issue. - fix: Correct usage of FileSystemUtility in the IOSBuilder. +>>>>>>> release-3.3.4 ## [3.3.3] - 2024-09-19 diff --git a/com.playeveryware.eos/Documentation~/command_line_export.md b/com.playeveryware.eos/Documentation~/command_line_export.md index f5774e22d..02c775308 100644 --- a/com.playeveryware.eos/Documentation~/command_line_export.md +++ b/com.playeveryware.eos/Documentation~/command_line_export.md @@ -7,7 +7,7 @@ This document outlines how to export the plugin from source using a command line ## BuildPackage -The following command generates a new `com.playeveryware.eos-[VERSION].tgz` file at the indicated output directory, the same exact way it would if you created a package via Unity Editor `Tools -> EOS Plugin -> Create Package` (and subsequently pressed "Create UPM Package"): +The following command generates a new `com.playeveryware.eos-[VERSION].tgz` file at the indicated output directory, the same exact way it would if you created a package via Unity Editor `EOS Plugin -> Advanced -> Create Package` (and subsequently pressed "Create UPM Package"): ``` Unity.exe -batchMode \ diff --git a/com.playeveryware.eos/Documentation~/configure_plugin.md b/com.playeveryware.eos/Documentation~/configure_plugin.md index 6d74091bc..cb37c73e6 100644 --- a/com.playeveryware.eos/Documentation~/configure_plugin.md +++ b/com.playeveryware.eos/Documentation~/configure_plugin.md @@ -5,27 +5,33 @@ To function, the plugin needs some information from your EOS project. Be sure to read the Epic Documentation on [getting started](https://dev.epicgames.com/docs/epic-account-services/getting-started?sessionInvalidated=true) with Epic Online Services. 1) Open your Unity project with the integrated EOS Unity Plugin. -2) In the Unity editor, Open ```Tools -> EOS Plugin -> Dev Portal Configuration```. +2) In the Unity editor, Open ```EOS Plugin -> EOS Configuration```. ![EOS Config Menu](/com.playeveryware.eos/Documentation~/images/dev-portal-configuration-editor-menu.png) ![EOS Config UI](/com.playeveryware.eos/Documentation~/images/eosconfig_ui.gif) -3) From the [Developer Portal](https://dev.epicgames.com/portal/), copy the configuration values listed below, and paste them into the similarly named fields in the editor tool window pictured above: +3) From the [Developer Portal](https://dev.epicgames.com/portal/), inside your game's `Product Settings` page, copy the configuration values listed below, and paste them into the similarly named fields in the editor tool window pictured above: > [!NOTE] > For more detailed information, check out Epic's Documentation on [Creating the Platform Interface](https://dev.epicgames.com/docs/game-services/eos-platform-interface#creating-the-platform-interface). * ProductName - * ProductVersion * [ProductID](https://dev.epicgames.com/docs/services/en-US/Glossary/index.html#D?term=ProductId) * [SandboxID](https://dev.epicgames.com/docs/services/en-US/Glossary/index.html#D?term=SandboxId) * [DeploymentID](https://dev.epicgames.com/docs/services/en-US/Glossary/index.html#D?term=DeploymentId) * [ClientSecret](https://dev.epicgames.com/docs/services/en-US/Glossary/index.html#D?term=ClientSecret) * [ClientID](https://dev.epicgames.com/docs/services/en-US/Glossary/index.html#D?term=ClientId) - * EncryptionKey -
+There are two other fields in the configuration editor. + +`ProductVersion` is a free-form numeric field, and can use any number. + + > [!WARNING] + > The `ProductVersion` field needs to have some value inside it; it cannot be left empty. + > Only use numeric values for the `ProductVersion` field. Do not include text such as "V1.0". + +`EncryptionKey` is used to encrypt uploads to the `Player Data Storage` and `Title Data Storage` EOS features. This value should be consistently used whenever uploading or downloading files from these Data Storages. > [!NOTE] > Click the "Generate" button to create a random key, if you haven't already configured an encryption key in the EOS portal. You can then add the generated key to the [Developer Portal](https://dev.epicgames.com/portal/). @@ -40,6 +46,4 @@ To function, the plugin needs some information from your EOS project. Be sure to - Attach `EOSManager.cs (Script)` to a Unity object, and it will initialize the plugin with the specified configuration in `OnAwake()` (this is what `Singletons.prefab` does). > [!NOTE] -The included [samples](http://github.com/PlayEveryWare/eos_plugin_for_unity/blob/development/com.playeveryware.eos/com.playeveryware.eos/README.md#samples) already have configuration values set for you to experiment with! - -If you would like to see specific examples of various EOS features in action, import the sample Unity scenes that are described below. +> The included [samples](http://github.com/PlayEveryWare/eos_plugin_for_unity/blob/development/com.playeveryware.eos/README.md#samples) already have configuration values set for you to experiment with! \ No newline at end of file diff --git a/com.playeveryware.eos/Documentation~/creating_the_upm_package.md b/com.playeveryware.eos/Documentation~/creating_the_upm_package.md index e49c53673..73f007dae 100644 --- a/com.playeveryware.eos/Documentation~/creating_the_upm_package.md +++ b/com.playeveryware.eos/Documentation~/creating_the_upm_package.md @@ -8,7 +8,7 @@ This tool (while a bit rough around the edges) allows for the generation of cust ## Steps to generate a UPM package -1. Go to `Tools -> EOS Plugin -> Create Package`. +1. Go to `EOS Plugin -> Advanced -> Create Package`. diff --git a/com.playeveryware.eos/Documentation~/dev_env/Ubuntu_Development_Environment.md b/com.playeveryware.eos/Documentation~/dev_env/Ubuntu_Development_Environment.md index 83c74b329..21e9b5538 100644 --- a/com.playeveryware.eos/Documentation~/dev_env/Ubuntu_Development_Environment.md +++ b/com.playeveryware.eos/Documentation~/dev_env/Ubuntu_Development_Environment.md @@ -95,7 +95,7 @@ Once Unity is open: 1. Go to File ➝ Build Settings 2. Switch the platform to "Dedicated Server." 3. Click on the button labeled "Player Settings..." at the bottom left. -4. Go to Tools ➝ EOS Automated Test Settings +4. Go to EOS Plugin ➝ Advanced ➝ EOS Automated Test Settings 5. Select the previously created `Builds/Server` directory for the "Test Server Directory" field. ## Help! I ran out of disk space! diff --git a/com.playeveryware.eos/Documentation~/easy_anticheat_configuration.md b/com.playeveryware.eos/Documentation~/easy_anticheat_configuration.md index b1ac32475..c74a7bf0c 100644 --- a/com.playeveryware.eos/Documentation~/easy_anticheat_configuration.md +++ b/com.playeveryware.eos/Documentation~/easy_anticheat_configuration.md @@ -12,7 +12,7 @@ The EOS plugin provides a method of automating EAC installation as part of the b ## Steps 1) Open your Unity project with the plugin installed. -2) Open the editor preferences settings for the plugin, which is avaiable in `Tools -> EOS Plugin -> Configuration`. All EAC options are located under the Tools subheader. +2) Open the editor preferences settings for the plugin, which is avaiable in `EOS Plugin -> Configuration`. All EAC options are located under the EOS Plugin subheader. 3) Enable EAC build steps by checking the `Use EAC` toggle. 4) Set `Path to EAC private key` and `Path to EAC Certificate` to the private and public keys you previously downloaded, respectively. 5) (Recommended) Set `Path to EAC splash image` to a 800x450 PNG that will display in the EAC bootstrapper. diff --git a/com.playeveryware.eos/Documentation~/frequently_asked_questions.md b/com.playeveryware.eos/Documentation~/frequently_asked_questions.md index 34d782936..48c287b0e 100644 --- a/com.playeveryware.eos/Documentation~/frequently_asked_questions.md +++ b/com.playeveryware.eos/Documentation~/frequently_asked_questions.md @@ -135,7 +135,7 @@ Overlay errors are most likely due to not having the overlay installed, this is Some native functionality are required for platform specific needs. -To get these dependent libraries, use the platform library build tool in the plugin at `Tools > EOS Plugin > Build Library > [Target Platform]` +To get these dependent libraries, use the platform library build tool in the plugin at `EOS Plugin > Advanced > Build Library > [Target Platform]` Or to install the libraries manually, go to the `lib/NativeCode` folder, find the target platform, and *build the `.sln`* or *`run the makefile`* in the folder. diff --git a/com.playeveryware.eos/Documentation~/getting_started_guide/README.md b/com.playeveryware.eos/Documentation~/getting_started_guide/README.md new file mode 100644 index 000000000..13aa2c130 --- /dev/null +++ b/com.playeveryware.eos/Documentation~/getting_started_guide/README.md @@ -0,0 +1,235 @@ +README.md + +# Full Guide to Implementing the EOS Plugin for Unity + +This document serves as the starting point for a beginning-to-end guide on how to implement the EOS Plugin for Unity into your game. +This will walk you through the steps to set up your project, supplemented by links to more detailed documentation should you want to explore things in greater detail. + +## Prerequisites + +This guide can be followed from a blank Unity project, or from an already implemented game. There are no steps required before you can start following this guide. + +See [the Supported Version of Unity Documentation](/com.playeveryware.eos/Documentation~/supported_versions_of_unity.md) to see if your version of Unity is supported. + +Your development team and the users of your game need to be able to access certain endpoints for the EOS Plugin for Unity to function. See [Epic's documentation on Firewall Considerations](https://dev.epicgames.com/docs/epic-online-services/eos-get-started/firewall-considerations) for a list of addresses. + + +# Signing Up for Epic Online Services + +The EOS Plugin for Unity requires you to have a configured account and game on the Epic Game Store portal. +This leads to [the Developer Portal landing page](https://dev.epicgames.com/portal/en-US/). +Pressing Sign In will present you with a screen where you can sign up for a new account. +If you already have an Epic Games Account, perhaps because you've played games on the Epic Games Store, you can sign in with that existing account. + +

You can either sign in to an existing account, or create a new account, on the Epic Developer Portal.
+You can either sign in to an existing account, or create a new account, on the Epic Developer Portal.

+ +After acquiring an account, if you are not using an existing Organization, you will be required to create an Organization. +An Organization is a holder for all of the Products you make. + +See [Epic's documentation on Organizations](https://dev.epicgames.com/docs/dev-portal/organization-management) for more information. + +## Working With an Existing Organization + +If you or a team member has already created an Organization, you can add Epic Game Store accounts to that Organization. +While signed in to the `Epic Games Developer Portal`, navigate to `Organization`. +Under the `Members` tab, you can use the `Invite New` button to send an invitation email to the target user. +That email will contain a link that will help the user sign up for an Epic Account if necessary, and then subsequently they will be added to your Organization. + +Everyone who will be testing your game should be a part of your Organization, or part of a Player Group. If they aren't, then whenever the EOS SDK requires the user to login, that user will be forbidden from accessing your application. + +For more information on Player Groups, see [Epic's documentation on Player Groups](https://dev.epicgames.com/docs/dev-portal/product-management#player-groups). + +# Developer Portal Configuration for Your Game + +Before bringing the EOS Plugin for Unity into your project, you must first configure your game in the Epic Developer Portal. +This section will cover the creation and configuration of a `Product`, `Client Policy`, `Client`, and `Application`. +During this process, the Developer Portal will create a set of `Sandbox` and `Deployment` environments for your game to use. + +> [!NOTE] +> While navigating the Epic Developer Portal to certain features, there will be dialogues to review and accept Epic terms and services. +> There are several of these throughout the onboarding process, with specific domains such as Epic Account Services. You will need to agree to the terms and services to use features that are part of those domains. +> This will bring you to a Terms of Service page with detailed information about the agreement. +> +> In order to utilize many features of Epic Online Services, you will need to review and agree to these conditions. +> In order to use any features under `Epic Games Store`, there will be an agreement process that includes a possible submission fee. +> While this agreement is required to perform Epic Game Store operations, including publishing and selling your game, the EOS Plugin for Unity can be configured and utilized before this is agreed to. +> +> For more information on the onboarding process, see [Epic's documentation on Get Started Overview](https://dev.epicgames.com/docs/epic-games-store/get-started/get-started-overview#onboarding-process). + +## 1) Create a Product + +By default, your Organization will not contain any Products. +While signed in to the `Epic Games Developer Portal` with your Organization selected, click the `Create Product` button underneath the `Your products` header to start the process. + +After naming your Product and creating it, there will be a brief wait as Epic's back-end servers process the creation. This is represented by a spinning loading animation. +When it's done, you can navigate to your Product to begin configuring it. + +For more information on Products, see [Epic's documentation on Products](https://dev.epicgames.com/docs/dev-portal/product-management#whats-a-product). + +## Client Policy and Client + +On the `Product Settings` page for your Product within the Epic Developer Portal, you will see sections for your Product, Clients, Sandboxes, and Deployments. + +

The first thing on the Product landing page is your Product, Clients, Sandboxes, and Deployments. Other than Product, these likely haven't been configured yet.
+The first thing on the Product landing page is your Product, Clients, Sandboxes, and Deployments. Other than Product, these likely haven't been configured yet.

+ +In order to get the most out of the EOS Plugin for Unity, you'll need to create and configure a Product, a Client, Sandboxes, and Deployments in the developer portal. +Navigate to the `Clients` section. There you'll see your list of Clients and Client policies, which will be empty. + +Clients are essentially types of contexts for your game. For example, all users running your game from the store will be assigned a Client that represents their abilities. +Each Client is assigned to one Client Policy. +Client Policies are configurable sets of rules for what permissions a Client can perform. For example, a Client Policy determines the EOS SDK's ability to look up information about the friend list for a particular player. + +## 2) Create a Client Policy + +The first thing to configure on the `Clients` screen is a Client Policy. To create a Client Policy, press the `Add new policy` button to bring up a dialogue. +The Client Policy needs a name, as well as select the details of a policy. + +If you select a preset, the permissions for that policy preset will be displayed in the dialogue. +The most permissive default policy is `GameClient /w UnlockAchievements`, which will give the Client Policy all of the permissions necessary to perform most actions. Consider using this `GameClient /w UnlockAchievements` policy setting if you don't have other specific needs. + +Custom policies can be defined here, and changed at any time. You don't need to commit to the final details at this time. Only Custom policies can have their details changed. + +## 3) Create a Client + +After the Client Policy is created, create a client. Press `Add new client` to bring up the Add new client dialogue. +This will require a name and a Client Policy. Use the previously created Client Policy. +At this time, it is not required that you fill the fields for either the `Trusted Server IP allow list` or the `Epic Account Service Redirect URL`. For information on Trusted Servers see [Epic's documentation on Linked Clients](https://dev.epicgames.com/docs/epic-account-services/getting-started#linked-clients). For information on the Redirect URL see [Epic's documentation on Web Applications](https://dev.epicgames.com/docs/web-api-ref/authentication#web-applications). + +For more information on Clients and Client Policies, see [Epic's Client Credential and Client Policy Management documentation](https://dev.epicgames.com/docs/dev-portal/client-credentials). + +## 4) Agreement of Epic Game Services Terms, Which Creates Sandboxes and Deployments + +The `Game Services` section of the portal covers features that are available to all games using the plugin, even if they don't require Epic Accounts to function. +At this time, you should navigate to the `Game Services` section. This will likely prompt you with a user agreement. + +Once accepted, `Sandboxes` and `Deployments` will be generated for `Dev` and `Stage`. These can be viewed in `Product Settings`. + +While not required for the plugin to function, many of the features of the EOS For Unity Plugin use Epic Game Services. +The `Dev` and `Stage` environment configurations will also help with developing your game by isolating the development environments from the `Live` environment. + +For more information on Sandboxes and Deployments, see [Epic's documentation on Sandboxes and Deployments](https://dev.epicgames.com/docs/dev-portal/product-management#sandboxes-and-deployments). + +## 5) Create an Application + +Navigate to the `Epic Account Services` page. The landing will show you all configured Applications, which are empty at first. +Use `Create Application` to get started. Immediately when you press this button, an Application will be created for you. +An Application is a collection of information about your game, which is later used to provide the Epic Game Store with essential information and permissions. +The `Create Application` button will bring up a dialogue with three major steps; `Brand Settings`, `Permissions`, and `Linked Clients`. + +`Brand Settings` contains information about your game such as the Application's website. +You do not need to fill out this page in order to start using the EOS Plugin for Unity; this information can be provided later. + +`Permissions` will set the essential information that each user needs to consent to in order to use your application. +This can be adjusted later. For full use of the EOS Plugin for Unity, consider marking `Online Presence` and `Friends` as Required. + +`Linked Clients` matches an Application to the Client that dictates its policies and further permissions. +Assign the Client created earlier to this. + +When you exit this dialogue, the created Application will appear in the Applications page. + +For more information on Applications, see [Epic's documentation on Applications in the Developer Portal Introduction](https://dev.epicgames.com/docs/dev-portal/dev-portal-intro#application). + +# Add the EOS Plugin for Unity to Your Game + +With a Client Policy, Client, and Application created, you're ready to start adding the EOS Plugin for Unity to your game. +Follow [our documentation for adding the plugin to your project](/com.playeveryware.eos/Documentation~/add_plugin.md). +Once successfully included, follow [the guidance on Importing the Samples](com.playeveryware.eos/Documentation~/samples.md). +In the EOS Plugin for Unity project, the provided code with the plugin will give you the ability to use EOS SDK's interfaces using a convenient C# wrapper. +The Samples come with scenes that demonstrate the usage of the EOS Plugin for Unity's Manager classes for each Sample's domain. + +Next, follow [our documentation regarding how to configure the plugin](/com.playeveryware.eos/Documentation~/configure_plugin.md). +Inside of Unity (using the menu bar), navigate to `EOS Plugin` -> `EOS Configuration`. + +

The EOS Configuration popup can be filled out using information in the Product Settings page.
+The EOS Configuration popup can be filled out using information in the Product Settings page.

+ +All of these values can be pulled from the `Product Settings` page and inner dialogues within the EOS Developer Portal. +One of the fields is your `Encryption Key`. This can remain its default value for now. This value will not be found in the EOS Developer Portal. +Essentially this value is used to encrypt Player Data Storage and Title Data Storage uploads, and needs to be consistent in order to decrypt those values when they are later downloaded. + +It is recommended that you use the `Dev` Sandbox and `Dev Deployment` Sandbox while configuring the plugin. +By using the Dev environment information, the EOS Plugin for Unity will use this environment when no other environment is assumed. +If your game is deployed through the Epic Game Store, the game will be launched with `-epicsandboxid` and `epicdeploymentid` arguments, which the EOS Plugin for Unity will use instead of your configured Sandbox and Deployment. + +For more information on the use of the Encryption Key, see [Epic's documentation on Title Storage Interface](https://dev.epicgames.com/docs/game-services/title-storage). + +## Starting to Use the Samples + +At this moment your game is set up to utilize the EOS Plugin for Unity. +The samples are documented [in the EOS Plugin for Unity Walkthrough documentation](https://github.com/PlayEveryWare/eos_plugin_for_unity/blob/stable/com.playeveryware.eos/Documentation~/Walkthrough.md), which leads to individual Scene walk throughs. +Assuming your Client Policy is set up to be permissive, consider validating the plugin inclusion by using [the Lobbies Sample](/com.playeveryware.eos/Documentation~/scene_walkthrough/lobbies_walkthrough.md). +Open the Lobbies sample in the scene, and start running the game. Note if there are any errors in the logs from the EOS SDK Plugin. + +The Lobbies sample is chosen because, assuming a permissive Client Policy, there's no needed further setup in the Epic Developer Portal. +If you successfully log in to the Lobbies Sample, create a Lobby to demonstrate that everything is connected appropriately. + +> [!NOTE] +> The samples include useful Managers and Services that will provide an easy way to use the EOS SDK. +> The scenes that are in the sample show how to utilize the plugin; even without needing those, it is still useful to bring in the samples. + +> [!NOTE] +> Look to the Unity logs for messages that relate to the configuration of the plugin. +> If there is an error relating to "Invalid Parameters" during initialization, view the EOS Configuration screen again. +> Ensure all fields have values, including `Product Version`, and that the values match what you have in the Epic Developer Portal's `Product Settings` page. + +> [!NOTE] +> When testing the Samples, ensure the account being logged in has access to your game. +> To start, use the same account that created the game, and either owns or is a member of the Organization. +> Only members of your Organization or Player Group with appropriate permissions are able to login to EOS from your game. +> +> For more information on Player Groups, see [Epic's documentation on Player Groups](https://dev.epicgames.com/docs/dev-portal/product-management#player-groups). + +# Utilizing the EOS Plugin for Unity + +The core of the EOS Plugin for Unity is the `EOSManager` class. This is a `MonoBehaviour` that manages the state of the EOS SDK. +To add it to your game, either use the `Singletons` prefab included with the Samples, or create a new GameObject and attach the `EOSManager.cs` script to it. +The `EOSManager` is designed to set the GameObject it is on as `DontDestroyOnLoad`, so it will persist between scenes. +If another `EOSManager` is created, perhaps because of a scene navigation, the original `EOSManager` persist, while the new instance will disable its own `MonoBehaviour`. + +`EOSManager` holds a static reference to an inner class `EOSSingleton`, which is where most of the plugin's state is held. This can be accessed with the static accessor `EOSManager.Instance`. + +## Logging In + +Before the plugin can be utilized, the user of your game needs to log in to EOS. +The samples include a `UILoginMenu` component in each of the scenes, which demonstrates handling logging in. +The kind of login your users should use is based on your game's platform, and the needs of your game. +See [our documentation on Login Types by Platform](/com.playeveryware.eos/Documentation~/login_type_by_platform.md) to determine the valid options for your platform. +Then follow [our documentation on Authenticating Players](/com.playeveryware.eos/Documentation~/player_authentication.md) to understand the login workflow. + +The most common use case is to follow the below process: + +- Use the Auth Interface to authenticate the user. This can be one of several methods, depending on your needs. For example `EOSManager.Instance.StartLoginWithLoginTypeAndToken`. + - If the authentication results in `Result.InvalidUser`, this indicates the user doesn't yet have a link from this Identity Provider to an Epic Account. This is not an error; any user who has never logged in with the provided credentials into an Epic service will return this result. Handle this by calling `EOSManager.Instance.AuthLinkExternalAccountWithContinuanceToken`, passing in the ContinuanceToken from the callback's `Epic.OnlineServices.Auth.LoginCallbackInfo` parameter. If that succeeds, the login flow can continue to the next step. The user will be authenticated by this function call. +- Use the Connect Interface to continue the login flow. Use `EOSManager.Instance.StartConnectLoginWithEpicAccount`. + - This can also return `Result.InvalidUser`, which indicates a Connect User needs to be created for this Epic Account to your title. This is also not an error; any user who is logging in for the first time to your game will encounter this. Use `EOSManager.Instance.CreateConnectUserWithContinuanceToken`, passing in the ContinuanceToken from the callback's `Epic.OnlineServices.Connect.LoginCallbackInfo` parameter. If this succeeds, then continue the login flow by calling `EOSManager.Instance.StartConnectLoginWithEpicAccount` again. + +If the above all succeeds, the plugin has logged the user in entirely, and the plugin is ready for use. +This is all demonstrated in the `UILoginMenu`'s handling of result codes. + +> [!NOTE] +> Many features in the EOS Plugin for Unity use a callback pattern for handling results. +> For example, `EOSManager.Instance.StartLoginWithLoginTypeAndToken` contains an `OnAuthLoginCallback` callback argument. +> +> These are usually delegates, with one parameter that describes the return type. For example, `OnAuthLoginCallback` has a `Epic.OnlineServices.Auth.LoginCallbackInfo` parameter. +> This contains information about whether the operation succeeded or failed through a `Result` enum. +> +> `UILoginMenu` demonstrates this in `StartConnectLoginWithEpicAccount`. It calls a function, providing a delegate which responds to the result of the operation. + +## Utilizing Managers and Interfaces + +All of the functionality for the EOS SDK is separated into interfaces. +These can be accessed, for example, through `EOSManager.Instance.GetEOSAchievementInterface()`. +This returns a C# wrapper around the EOS SDK's operations. + +When the Samples are included into your project, all of these interfaces are accessible through Manager and Service classes. +These Managers and Services are designed to make using the EOS SDK's functions feel "more Unity-like" and be more convenient than accessing the Interfaces directly. + +For example, the functionality of `FriendsInterface` is made into convenient functions inside `EOSFriendsManager`. +To get a `Manager` class, for example the `EOSFriendsManager`, use `EOSManager.Instance.GetOrCreateManager()`. + +Some of the interfaces are wrapped in `Service`s. For example, `AchievementsInterface` has its functionality inside `AchievementsService`. +These can be accessed through their lazy-loaded Singleton reference. For example, `AchievementsService.Instance`. + +The Managers and Services in the plugin manage their own states. Other than the `EOSManager`, none of them are MonoBehaviours, and will clean up their data on login and logout operations automatically. \ No newline at end of file diff --git a/com.playeveryware.eos/Documentation~/getting_started_guide/epic_developerportal_productlanding.png b/com.playeveryware.eos/Documentation~/getting_started_guide/epic_developerportal_productlanding.png new file mode 100644 index 000000000..8e898485a Binary files /dev/null and b/com.playeveryware.eos/Documentation~/getting_started_guide/epic_developerportal_productlanding.png differ diff --git a/com.playeveryware.eos/Documentation~/getting_started_guide/epic_developerportal_signin_or_createaccount.png b/com.playeveryware.eos/Documentation~/getting_started_guide/epic_developerportal_signin_or_createaccount.png new file mode 100644 index 000000000..1c6a5ff95 Binary files /dev/null and b/com.playeveryware.eos/Documentation~/getting_started_guide/epic_developerportal_signin_or_createaccount.png differ diff --git a/com.playeveryware.eos/Documentation~/getting_started_guide/plugin_eosconfiguration.png b/com.playeveryware.eos/Documentation~/getting_started_guide/plugin_eosconfiguration.png new file mode 100644 index 000000000..798b77d08 Binary files /dev/null and b/com.playeveryware.eos/Documentation~/getting_started_guide/plugin_eosconfiguration.png differ diff --git a/com.playeveryware.eos/Documentation~/google_signin.md b/com.playeveryware.eos/Documentation~/google_signin.md new file mode 100644 index 000000000..93cfa076f --- /dev/null +++ b/com.playeveryware.eos/Documentation~/google_signin.md @@ -0,0 +1,251 @@ +README.md + +#
Connect Login with GoogleID using Credential Manager
+--- + +## Overview + +This document describes how to enable Connect Login with GoogleID in conjunction with Epic Online Services Plugin for Unity. +We followed [Authenticate users with Sign in with Google](https://developer.android.com/identity/sign-in/credential-manager-siwg) and implemented some scripts for the users to easily achieve the feature. + +## Setup + +### Plugins + +The feature is included in our samples, so no external plugin is needed. + +### Build Settings + +To enable the feature, follow the build configuration step below. +Add the following in the dependencies of the project's main gradle file. (The default is `mainTemplate.gradle`) +``` + implementation 'androidx.credentials:credentials-play-services-auth:1.3.0' + implementation 'androidx.credentials:credentials:1.3.0' + implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1' +``` + +### Configurations + +Configure `GoogleLoginClientID` and optionally `GoogleLoginNonce` in `AndroidConfig`. + +The `GoogleLoginClientID` could be found from `https://console.cloud.google.com/apis/`. +Follow [Set up your Google APIs console project](https://developer.android.com/identity/sign-in/credential-manager-siwg#set-google) to set up your project. + +> [!IMPORTANT] +> Make sure `GoogleLoginClientID` uses the ClientID of the Web Application type. + +## Scripts + +### Sample Sign In Scripts + +Here are some sample scripts tested in PlayEveryWare EOS Unity Plugin + +Starting with the java calls of google APIs + +```java +package com.playeveryware.googlelogin; + +import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption; +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; +import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException; + +import androidx.credentials.GetCredentialRequest; +import androidx.credentials.GetCredentialResponse; +import androidx.credentials.exceptions.GetCredentialException; +import androidx.credentials.Credential; +import androidx.credentials.CustomCredential; +import androidx.credentials.CredentialManager; +import androidx.credentials.CredentialManagerCallback; + +import android.app.Activity; +import android.content.Context; +import android.os.CancellationSignal; +import android.util.Log; + +import java.util.concurrent.Executors; + + +public class login extends Activity +{ + private String name; + private String token; + private static login instance; + + public login() + { + this.instance = this; + } + public static login instance() + { + if(instance == null) + { + instance = new login(); + } + return instance; + } + + public String getResultName() + { + return name; + } + public String getResultIdToken() + { + return token; + } + + public void SignInWithGoogle(String clientID, String nonce, Context context, CredentialManagerCallback callback) + { + GetSignInWithGoogleOption signInWithGoogleOption = new GetSignInWithGoogleOption.Builder(clientID) + .setNonce(nonce) + .build(); + + GetCredentialRequest request = new GetCredentialRequest.Builder() + .addCredentialOption(signInWithGoogleOption) + .build(); + + CredentialManager credentialManager = CredentialManager.create(this); + credentialManager.getCredentialAsync(context,request,new CancellationSignal(),Executors.newSingleThreadExecutor(),callback); + } + + public void handleFailure(GetCredentialException e) + { + Log.e("Unity", "Received an invalid google id token response", e); + } + + public void handleSignIn(GetCredentialResponse result) + { + Credential credential = result.getCredential(); + + if (credential instanceof CustomCredential) + { + if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) + { + try + { + GoogleIdTokenCredential googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData()); + name = googleIdTokenCredential.getDisplayName(); + token = googleIdTokenCredential.getIdToken(); + } + catch (Exception e) + { + if (e instanceof GoogleIdTokenParsingException) + { + Log.e("Unity", "Received an invalid google id token response", e); + } + else + { + Log.e("Unity", "Some exception", e); + } + } + } + } + } +} +``` + +Next we create the C# wrappers that calls the java code, and use `AndroidJavaProxy` to implement the login callbacks in C# instead of java + +```cs +using UnityEngine; + +namespace PlayEveryWare.EpicOnlineServices.Samples +{ + public class SignInWithGoogleManager : MonoBehaviour + { + AndroidJavaObject loginObject; + + public void GetGoogleIdToken(System.Action callback) + { + using AndroidJavaClass unityPlayer = new("com.unity3d.player.UnityPlayer"); + using AndroidJavaObject activity = unityPlayer.GetStatic("currentActivity"); + if (activity == null) + { + Debug.LogError("EOSAndroid: activity context is null!"); + return; + } + + using AndroidJavaClass loginClass = new AndroidJavaClass("com.playeveryware.googlelogin.login"); + if (loginClass == null) + { + Debug.LogError("Java Login Class is null!"); + return; + } + + loginObject = loginClass.CallStatic("instance"); + + /// Create the proxy class and pass instances to be used by the callback + EOSCredentialManagerCallback javaCallback = new EOSCredentialManagerCallback(); + javaCallback.loginObject = loginObject; + javaCallback.callback = callback; + + AndroidConfig config = AndroidConfig.Get(); + + if (string.IsNullOrEmpty(config.GoogleLoginClientID)) + { + Debug.LogError("Client ID is null, needs to be configured for Google ID connect login"); + return; + } + + /// SignInWithGoogle(String clientID, String nonce, Context context, CredentialManagerCallback callback) + loginObject.Call("SignInWithGoogle", config.GoogleLoginClientID, config.GoogleLoginNonce, activity, javaCallback); + } + + class EOSCredentialManagerCallback : AndroidJavaProxy + { + public AndroidJavaObject loginObject; + public System.Action callback; + + /// + /// Proxy class to receive Android callbacks in C# + /// + public EOSCredentialManagerCallback() : base("androidx.credentials.CredentialManagerCallback") { } + + /// + /// Succeeding Callback of GetCredentialAsync + /// GetCredentialAsync is called in com.playeveryware.googlelogin.login.SignInWithGoogle) + /// + /// + public void onResult(AndroidJavaObject credentialResponseResult) + { + /// Parses the response resilt into google credentials + loginObject.Call("handleSignIn", credentialResponseResult); + + /// Invoke Connect Login with fetched Google ID + callback.Invoke(loginObject.Call("getResultIdToken"), loginObject.Call("getResultName")); + } + + /// + /// Failing Callback of GetCredentialAsync + /// GetCredentialAsync is called in com.playeveryware.googlelogin.login.SignInWithGoogle) + /// + /// + public void onError(AndroidJavaObject credentialException) + { + loginObject.Call("handleFailure", credentialException); + callback.Invoke(null, null); + } + } + } +} +``` + +Finally the method that triggers the login process. +Example from PlayEveryWare EOS Unity Plugin's `UILoginMenu.cs` + +```cs + private void ConnectGoogleId() + { + SignInWithGoogleManager signInWithGoogleManager = new(); + + signInWithGoogleManager.GetGoogleIdToken((string token, string username) => + { + if (string.IsNullOrEmpty(token)) + { + Debug.LogError("Failed to retrieve Google Id Token"); + return; + } + StartConnectLoginWithToken(ExternalCredentialType.GoogleIdToken, token, username); + }); + } +``` + diff --git a/com.playeveryware.eos/Documentation~/images/create-package-menu.png b/com.playeveryware.eos/Documentation~/images/create-package-menu.png index 4513a53f9..0e0a1132c 100644 Binary files a/com.playeveryware.eos/Documentation~/images/create-package-menu.png and b/com.playeveryware.eos/Documentation~/images/create-package-menu.png differ diff --git a/com.playeveryware.eos/Documentation~/images/dev-portal-configuration-editor-menu.png b/com.playeveryware.eos/Documentation~/images/dev-portal-configuration-editor-menu.png index 9b8554e89..188e26295 100644 Binary files a/com.playeveryware.eos/Documentation~/images/dev-portal-configuration-editor-menu.png and b/com.playeveryware.eos/Documentation~/images/dev-portal-configuration-editor-menu.png differ diff --git a/com.playeveryware.eos/Documentation~/macOS/README_macOS.md b/com.playeveryware.eos/Documentation~/macOS/README_macOS.md index 34369ab7b..51f02a309 100644 --- a/com.playeveryware.eos/Documentation~/macOS/README_macOS.md +++ b/com.playeveryware.eos/Documentation~/macOS/README_macOS.md @@ -25,7 +25,7 @@ If you want to do that work manually, please refer to the following: ### Building Native Libraries -* Run `Tools -> EOS Plugin -> Build Libraries -> Mac` to build the dylibs needed for the mac build. +* Run `EOS Plugin -> Advanced -> Build Libraries -> Mac` to build the dylibs needed for the mac build. * Set the path of `make` at `Edit -> Preferences -> EOS Plugin -> Platform Library Build Settings -> Make path` * By default the path is `/usr/bin/make` or `usr/local/bin/make` * Alternatively, manually use the makefile in `lib/NativeCode/DynamicLibraryLoaderHelper_macOS/` by opening the terminal at the folder and running the command: diff --git a/com.playeveryware.eos/Documentation~/supported_versions_of_unity.md b/com.playeveryware.eos/Documentation~/supported_versions_of_unity.md index 1420e2baf..14340f15c 100644 --- a/com.playeveryware.eos/Documentation~/supported_versions_of_unity.md +++ b/com.playeveryware.eos/Documentation~/supported_versions_of_unity.md @@ -3,8 +3,6 @@ #
Supported Versions of Unity
--- -Due to Unity's versioning there is a "current supported" version. Modestly newer or older versions may work without error, work with minor tweaks, or require a prohibitively large amount of work to function properly. +The plugin is developed against [Unity 2021.3.16f1](https://dev.epicgames.com/docs/epic-online-services/release-notes#1163-cl32303053---2024-apr-09). Modestly newer or older versions may work without error or with minor tweaks. -For that reason, this plugin specifically targets the following version of the Unity Editor: [Unity 2021.3.16f1](https://dev.epicgames.com/docs/epic-online-services/release-notes#1163-cl32303053---2024-apr-09). - -Versions of the Unity Editor that start with 2021 will _usually_ work without error, but other versions are not officially supported at this time. \ No newline at end of file +Our plugin team is available to help with any integration issues relating to other versions of Unity. If you encounter any difficulties, please feel free to create an issue in this repository with information about the version of Unity your project uses and the problems encountered. The team will address the issue as soon as possible to get your particular version of Unity up and running. \ No newline at end of file diff --git a/com.playeveryware.eos/Documentation~/upgrading_to_new_versions_of_eos.md b/com.playeveryware.eos/Documentation~/upgrading_to_new_versions_of_eos.md index 8b30fe1b2..641ee7b6b 100644 --- a/com.playeveryware.eos/Documentation~/upgrading_to_new_versions_of_eos.md +++ b/com.playeveryware.eos/Documentation~/upgrading_to_new_versions_of_eos.md @@ -57,7 +57,7 @@ new plugin can be generated. These steps are for upgrading the EOS SDK as a maintainer of the repo. There is a tool that one can use install new versions of the SDK, located under -`Tools -> EOS Plugin -> Install EOS Zip` +`EOS Plugin -> Install EOS Zip` It requires a JSON description file to direct it where to put the files in the zip, and a zip file that contains the SDK. The latest version of the SDK can be downloaded from diff --git a/com.playeveryware.eos/Runtime/Core/Common.meta b/com.playeveryware.eos/Runtime/Core/Common.meta new file mode 100644 index 000000000..5516ef28e --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05948c8e1aeafa64794f3ee13c161e95 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Extensions.meta b/com.playeveryware.eos/Runtime/Core/Common/Extensions.meta similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Extensions.meta rename to com.playeveryware.eos/Runtime/Core/Common/Extensions.meta diff --git a/com.playeveryware.eos/Runtime/Core/Extensions/FileInfoExtensions.cs b/com.playeveryware.eos/Runtime/Core/Common/Extensions/FileInfoExtensions.cs similarity index 96% rename from com.playeveryware.eos/Runtime/Core/Extensions/FileInfoExtensions.cs rename to com.playeveryware.eos/Runtime/Core/Common/Extensions/FileInfoExtensions.cs index c0115cc4d..146f09636 100644 --- a/com.playeveryware.eos/Runtime/Core/Extensions/FileInfoExtensions.cs +++ b/com.playeveryware.eos/Runtime/Core/Common/Extensions/FileInfoExtensions.cs @@ -23,7 +23,7 @@ // Comment or uncomment the following definition to turn on or off debug statements // #define ENABLE_FILEINFO_EXTENSIONS_DEBUG -namespace PlayEveryWare.EpicOnlineServices.Extensions +namespace PlayEveryWare.Common.Extensions { using System; using System.Diagnostics; @@ -31,6 +31,11 @@ namespace PlayEveryWare.EpicOnlineServices.Extensions using System.Security.Cryptography; using System.Text; +#if EXTERNAL_TO_UNITY + using Debug = PlayEveryWare.EpicOnlineServices.Debug; +#else + using Debug = UnityEngine.Debug; +#endif public static class FileInfoExtensions { /// @@ -69,7 +74,7 @@ private static void LogInequalityReason(FileInfo one, FileInfo two, string reaso sb.AppendLine($"File #1: \"{one.FullName}\""); sb.AppendLine($"File #2: \"{two.FullName}\""); - UnityEngine.Debug.Log(sb.ToString()); + Debug.Log(sb.ToString()); } /// diff --git a/com.playeveryware.eos/Runtime/Core/Extensions/FileInfoExtensions.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Extensions/FileInfoExtensions.cs.meta similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Extensions/FileInfoExtensions.cs.meta rename to com.playeveryware.eos/Runtime/Core/Common/Extensions/FileInfoExtensions.cs.meta diff --git a/com.playeveryware.eos/Runtime/Core/Extensions/ListExtensions.cs b/com.playeveryware.eos/Runtime/Core/Common/Extensions/ListExtensions.cs similarity index 98% rename from com.playeveryware.eos/Runtime/Core/Extensions/ListExtensions.cs rename to com.playeveryware.eos/Runtime/Core/Common/Extensions/ListExtensions.cs index b23d48957..72164e0af 100644 --- a/com.playeveryware.eos/Runtime/Core/Extensions/ListExtensions.cs +++ b/com.playeveryware.eos/Runtime/Core/Common/Extensions/ListExtensions.cs @@ -20,7 +20,7 @@ * SOFTWARE. */ -namespace PlayEveryWare.EpicOnlineServices.Extensions +namespace PlayEveryWare.Common.Extensions { using System; using System.Collections.Generic; diff --git a/com.playeveryware.eos/Runtime/Core/Extensions/ListExtensions.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Extensions/ListExtensions.cs.meta similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Extensions/ListExtensions.cs.meta rename to com.playeveryware.eos/Runtime/Core/Common/Extensions/ListExtensions.cs.meta diff --git a/com.playeveryware.eos/Runtime/Core/Extensions/StringExtensions.cs b/com.playeveryware.eos/Runtime/Core/Common/Extensions/StringExtensions.cs similarity index 96% rename from com.playeveryware.eos/Runtime/Core/Extensions/StringExtensions.cs rename to com.playeveryware.eos/Runtime/Core/Common/Extensions/StringExtensions.cs index 43cb5c33f..d0862d387 100644 --- a/com.playeveryware.eos/Runtime/Core/Extensions/StringExtensions.cs +++ b/com.playeveryware.eos/Runtime/Core/Common/Extensions/StringExtensions.cs @@ -20,7 +20,7 @@ * SOFTWARE. */ -namespace PlayEveryWare.EpicOnlineServices.Extensions +namespace PlayEveryWare.Common.Extensions { public static class StringExtensions { diff --git a/com.playeveryware.eos/Runtime/Core/Extensions/StringExtensions.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Extensions/StringExtensions.cs.meta similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Extensions/StringExtensions.cs.meta rename to com.playeveryware.eos/Runtime/Core/Common/Extensions/StringExtensions.cs.meta diff --git a/com.playeveryware.eos/Runtime/Core/Common/Named.cs b/com.playeveryware.eos/Runtime/Core/Common/Named.cs new file mode 100644 index 000000000..151588725 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Named.cs @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.Common +{ + using EpicOnlineServices.Utility; + using System; + using System.Collections.Generic; + + /// + /// Used to associate a name with a particular type of value. + /// + public class Named : IEquatable>, IComparable>, IEquatable where T : IEquatable + { + public event EventHandler> NameChanged; + + /// + /// The name of the value (typically used for things like UI labels) + /// + public string Name { get; private set; } + + /// + /// The value itself. + /// + public T Value; + + /// + /// Create a new Named with the given name and value. + /// + /// The name for the value. + /// The value. + public Named(string name, T value) + { + Name = name; + Value = value; + } + + public bool TrySetName(string newName, bool notify = true) + { + // If the name hasn't changed, do nothing and return true. + if (string.Equals(Name, newName)) + { + return true; + } + + string oldName = Name; + Name = newName; + if (notify) + { + NameChanged?.Invoke(this, new ValueChangedEventArgs(oldName, newName)); + } + + // If the name has not been set to the new name then it was + // not able to be set. + return string.Equals(Name, newName); + } + + /// + /// Compares one named value to another. This is purely for sorting + /// purposes, so only the Name component of the value is compared. + /// + /// + /// The other named object (must be of same type). + /// + /// + /// Result of a comparison between the names of each Named object. + /// + public int CompareTo(Named other) + { + return string.CompareOrdinal(Name, other.Name); + } + + /// + /// Compare the equality of one Named instance a value of the same type. + /// Only the value of the Named instance is considered when evaluating + /// equivalency. + /// + /// + /// The value to evaluate equality for. + /// + /// + /// True if the given value is equivalent to the named value instance. + /// + public bool Equals(T other) + { + if (Value == null && other == null) + return true; + + return Value != null && Value.Equals(other); + } + + public override bool Equals(object obj) + { + return obj is Named other && Equals(other); + } + + public bool Equals(Named other) + { + if (other is null) + { + return false; + } + + return ReferenceEquals(this, other) || EqualityComparer.Default.Equals(Value, other.Value); + } + + public override int GetHashCode() + { + return HashUtility.Combine(Value); + } + + public override string ToString() + { + return $"{Name} : {Value}"; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Common/Named.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Named.cs.meta new file mode 100644 index 000000000..f8d8da96e --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Named.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d4e51a344e33fe42a36fb1aa92faff9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Common/SetOfNamed.cs b/com.playeveryware.eos/Runtime/Core/Common/SetOfNamed.cs new file mode 100644 index 000000000..71fcf0b4e --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/SetOfNamed.cs @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.Common +{ + using Newtonsoft.Json; + using System.Collections.Generic; + using System; + using System.Collections; + + /// + /// Class that stores a unique set of values, where each value has a name + /// associated to it. Items are sorted by the name. No two items of the same + /// value can be added (regardless of if they have different names). Also, + /// no two items can have the same name (regardless of if they have + /// different values). + /// + /// The type being wrapped with a name. + public class SetOfNamed : List> where T : IEquatable, new() + { + /// + /// When items without a name are added to the SortedSet, this is the + /// default name to give to the newly added item. When an item with the + /// default name already exists in the set, then the default name is + /// appended with an increasing number until the resulting name does + /// not exist in the collection. + /// + private readonly string _defaultNamePattern; + + /// + /// Used to store a function that can be used to determine if an item + /// can be removed or not. + /// + private Func _removePredicate = null; + + /// + /// Copy of the names of the items that exist within the collection, to + /// test for uniqueness in a performant manner. + /// + private readonly HashSet _existingNames = new(); + + /// + /// Creates a new SortedSetOfNamed. + /// + /// + /// The default name pattern to apply to items being added to the set. + /// + public SetOfNamed(string defaultNamePattern = "NamedItem") + { + _defaultNamePattern = defaultNamePattern; + } + + /// + /// Set the predicate used to determine if an item can be removed from + /// the collection. + /// + /// + /// A function that can determine whether or not an item can be removed. + /// + public void SetRemovePredicate(Func removePredicate) + { + _removePredicate = removePredicate; + } + + /// + /// Returns the name of a new item to be added to the collection. + /// + /// The name to give to the added set. + private string GetNewItemName() + { + int left = 1; + int right = Count; + string newItemName = _defaultNamePattern; + + while (left <= right) + { + int mid = left + (right - left) / 2; + string testName = $"{_defaultNamePattern} ({mid})"; + + if (ContainsName(testName)) + { + // If name exists, the smallest valid increment must be greater than mid + left = mid + 1; + } + else + { + // If name does not exist, it might be the smallest increment + newItemName = testName; + right = mid - 1; + } + } + + return newItemName; + } + + /// + /// Adds an item to the sorted set with a default name. + /// + /// + /// The value to add to the SortedSet. + /// + /// + /// True if the item was able to be added, false otherwise. + /// + public bool Add(T value = default) + { + // If value is null, then set it to either be default or a new + // instance of the value type. This is useful because in the case + // where T is a ReferenceType, it's "default" is null, and we want + // to make sure that it is instead a new (empty) instance of the + // reference type. + value ??= typeof(T).IsValueType ? default : new T(); + + // Determines the name of the new item. + string newItemName = GetNewItemName(); + + // Add to the collection using the base implementation, so long as + // the name for the new item is not used for another named item + // already in the collection, and so long as the value is not the + // same as the value of another item in the collection. + if (ContainsName(newItemName) || ContainsValue(value)) + { + return false; + } + + Named newItem = new(newItemName, value); + newItem.NameChanged += OnItemNameChanged; + + // Add name to internal hash set used to ensure uniqueness. + _existingNames.Add(newItem.Name); + + Add(newItem); + return true; + } + + private void OnItemNameChanged(object sender, ValueChangedEventArgs e) + { + // If the sender is not of the correct type, or if the value hasn't changed, then take no action. + if (sender is not Named item || string.Equals(e.OldValue, e.NewValue)) + { + return; + } + + // If the collection already contains an item with the name + if (ContainsName(e.NewValue)) + { + // Set the name back to the old value. The return value can be + // discarded, because it is always true if notify is set to + // false. + _ = item.TrySetName(e.OldValue, false); + } + else + { + // Remove old name from set used to ensure name uniqueness. + _existingNames.Remove(e.OldValue); + // Add the new name to the set used to ensure name uniqueness. + _existingNames.Add(e.NewValue); + } + } + + /// + /// Determines whether there is an item in the collection with the + /// indicated name. + /// + /// + /// The name to check. + /// + /// + /// True if there is an item in the collection with the given name, + /// false otherwise. + /// + private bool ContainsName(string name) + { + return _existingNames.Contains(name); + } + + /// + /// Determines whether there is an item in the collection with the + /// indicated value. + /// + /// + /// The value to check. + /// + /// + /// True if there is an item in the collection with the indicated value, + /// false otherwise. + /// + private bool ContainsValue(T item) + { + foreach (Named namedItem in this) + { + // Keep going if the value is not the same + if (namedItem.Value.Equals(item)) + { + return true; + } + } + + return false; + } + + public new bool Remove(Named item) + { + // If a predicate is not defined, or the predicate returns false, + // then return false and stop. + if (_removePredicate != null && !_removePredicate(item.Value)) + { + return false; + } + + // Remove the event subscription + item.NameChanged -= OnItemNameChanged; + + // Otherwise attempt to remove the item + base.Remove(item); + + return true; + } + } + +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Common/SetOfNamed.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/SetOfNamed.cs.meta new file mode 100644 index 000000000..4e3da97bb --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/SetOfNamed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1eb6c6e73721c084aa4cb042b1aaaeb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Common/Utility.meta b/com.playeveryware.eos/Runtime/Core/Common/Utility.meta new file mode 100644 index 000000000..4de544cf0 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Utility.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd44929369be06a428e97cf86b39a781 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Utility/EnumUtility.cs b/com.playeveryware.eos/Runtime/Core/Common/Utility/EnumUtility.cs similarity index 92% rename from com.playeveryware.eos/Runtime/Core/Utility/EnumUtility.cs rename to com.playeveryware.eos/Runtime/Core/Common/Utility/EnumUtility.cs index 1d96645c3..2f495891a 100644 --- a/com.playeveryware.eos/Runtime/Core/Utility/EnumUtility.cs +++ b/com.playeveryware.eos/Runtime/Core/Common/Utility/EnumUtility.cs @@ -27,7 +27,10 @@ namespace PlayEveryWare.EpicOnlineServices.Utility { using System; using System.Collections.Generic; + +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif /// /// Provides support for alternate string parsing of enum values. @@ -240,5 +243,31 @@ public static IEnumerable GetEnumerator(TEnum bitFlag) return compositeParts; } + + /// + /// Gets the lowest value in an enum type. + /// + /// + /// The lowest type within an enum. If the enum is empty, default is + /// returned. + /// + public static TEnum GetLowest() + { + object lowest = null; + foreach (var value in Enum.GetValues(typeof(TEnum))) + { + if (lowest == null || Comparer.Default.Compare(value, lowest) < 0) + { + lowest = value; + } + } + + if (lowest != null) + { + return (TEnum)Enum.ToObject(typeof(TEnum), lowest); + } + + return default; + } } } \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Utility/EnumUtility.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Utility/EnumUtility.cs.meta similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Utility/EnumUtility.cs.meta rename to com.playeveryware.eos/Runtime/Core/Common/Utility/EnumUtility.cs.meta diff --git a/Assets/Plugins/Source/Editor/Platforms/iOS/IOSConfigEditor.cs b/com.playeveryware.eos/Runtime/Core/Common/Utility/HashUtility.cs similarity index 62% rename from Assets/Plugins/Source/Editor/Platforms/iOS/IOSConfigEditor.cs rename to com.playeveryware.eos/Runtime/Core/Common/Utility/HashUtility.cs index 71d9218c4..083c7eee5 100644 --- a/Assets/Plugins/Source/Editor/Platforms/iOS/IOSConfigEditor.cs +++ b/com.playeveryware.eos/Runtime/Core/Common/Utility/HashUtility.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 PlayEveryWare + * Copyright (c) 2024 PlayEveryWare * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -8,8 +8,8 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -20,9 +20,27 @@ * SOFTWARE. */ -namespace PlayEveryWare.EpicOnlineServices.Editor +namespace PlayEveryWare.EpicOnlineServices.Utility { - public class IOSConfigEditor : PlatformConfigEditor + using System; + + public static class HashUtility { + public static int Combine(params object[] fields) + { +#if NET_STANDARD_2_0 + return HashCode.Combine(fields); +#else + int hash = 17; + + foreach (object field in fields) + { + int fieldHash = field != null ? field.GetHashCode() : 0; + hash = hash * 31 + fieldHash; + } + + return hash; +#endif + } } } \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Common/Utility/HashUtility.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Utility/HashUtility.cs.meta new file mode 100644 index 000000000..c6b83a086 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Utility/HashUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f47ee7cbb51446c4e8243cbb72090d59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Utility/SafeTranslatorUtility.cs b/com.playeveryware.eos/Runtime/Core/Common/Utility/SafeTranslatorUtility.cs similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Utility/SafeTranslatorUtility.cs rename to com.playeveryware.eos/Runtime/Core/Common/Utility/SafeTranslatorUtility.cs diff --git a/com.playeveryware.eos/Runtime/Core/Utility/SafeTranslatorUtility.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Utility/SafeTranslatorUtility.cs.meta similarity index 100% rename from com.playeveryware.eos/Runtime/Core/Utility/SafeTranslatorUtility.cs.meta rename to com.playeveryware.eos/Runtime/Core/Common/Utility/SafeTranslatorUtility.cs.meta diff --git a/com.playeveryware.eos/Runtime/Core/Common/Utility/VersionUtility.cs b/com.playeveryware.eos/Runtime/Core/Common/Utility/VersionUtility.cs new file mode 100644 index 000000000..69f45af80 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Utility/VersionUtility.cs @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.Common +{ + using System; + + public static class VersionUtility + { + /// + /// Compares two versions for equality, ensuring that if one version has + /// a component defined and the other one does not, they are considered + /// not equal. + /// + /// The first version. + /// The second version. + /// + /// True if both versions are considered equal, considering only defined + /// components. If one version has a component defined and the other + /// does not, they are considered not equal. + /// + public static bool AreVersionsEqual(Version v1, Version v2) + { + // Compare major versions (must both be defined and equal) + if (v1.Major != v2.Major) return false; + + // Compare minor versions (must both be defined and equal) + if (v1.Minor != v2.Minor) return false; + + // Compare build versions + if ((v1.Build != -1 && v2.Build == -1) || (v1.Build == -1 && v2.Build != -1)) + { + return false; // One is defined, the other is not + } + if (v1.Build != -1 && v2.Build != -1 && v1.Build != v2.Build) + { + return false; // Both are defined, but not equal + } + + // Compare revision versions + if ((v1.Revision != -1 && v2.Revision == -1) || (v1.Revision == -1 && v2.Revision != -1)) + { + return false; // One is defined, the other is not + } + if (v1.Revision != -1 && v2.Revision != -1 && v1.Revision != v2.Revision) + { + return false; // Both are defined, but not equal + } + + // If all specified parts are equal, return true + return true; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Common/Utility/VersionUtility.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Utility/VersionUtility.cs.meta new file mode 100644 index 000000000..7ba731630 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Utility/VersionUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17450da72951be84a971bacae983abc4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Source/Editor/Platforms/macOS/MacOSConfigEditor.cs b/com.playeveryware.eos/Runtime/Core/Common/ValueChangedEventArgs.cs similarity index 75% rename from Assets/Plugins/Source/Editor/Platforms/macOS/MacOSConfigEditor.cs rename to com.playeveryware.eos/Runtime/Core/Common/ValueChangedEventArgs.cs index 6564079e3..936e62239 100644 --- a/Assets/Plugins/Source/Editor/Platforms/macOS/MacOSConfigEditor.cs +++ b/com.playeveryware.eos/Runtime/Core/Common/ValueChangedEventArgs.cs @@ -20,9 +20,19 @@ * SOFTWARE. */ -namespace PlayEveryWare.EpicOnlineServices.Editor +namespace PlayEveryWare.Common { - public class MacOSConfigEditor : PlatformConfigEditor + using System; + + public class ValueChangedEventArgs : EventArgs { + public readonly TValueType OldValue; + public readonly TValueType NewValue; + + public ValueChangedEventArgs(TValueType oldValue, TValueType newValue) + { + OldValue = oldValue; + NewValue = newValue; + } } -} \ No newline at end of file +} diff --git a/com.playeveryware.eos/Runtime/Core/Common/ValueChangedEventArgs.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/ValueChangedEventArgs.cs.meta new file mode 100644 index 000000000..8dc5cf9ea --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/ValueChangedEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 63e82180d9bf73b47817f829467ad0b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Common/Wrapped.cs b/com.playeveryware.eos/Runtime/Core/Common/Wrapped.cs new file mode 100644 index 000000000..d3ac1dfc1 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Wrapped.cs @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.Common +{ + + /// + /// Provides functionality for a class that can wrap a struct value type. + /// + /// + /// A struct type to wrap in a reference type. + /// + public abstract class Wrapped where T : struct + { + /// + /// The underlying value that is being wrapped. + /// + protected T _value; + + /// + /// Return the wrapped value. + /// + /// The value that is wrapped by this reference. + public T Unwrap() + { + return _value; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Common/Wrapped.cs.meta b/com.playeveryware.eos/Runtime/Core/Common/Wrapped.cs.meta new file mode 100644 index 000000000..f9154fe77 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Common/Wrapped.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49e52f99450b0234d925e2c4ea17a706 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldAttribute.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldAttribute.cs index 20b2df022..4b263bce0 100644 --- a/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldAttribute.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldAttribute.cs @@ -24,9 +24,9 @@ namespace PlayEveryWare.EpicOnlineServices { using System; - [AttributeUsage(AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class ConfigFieldAttribute : Attribute - { + { /// /// The label for the config field. /// @@ -35,26 +35,47 @@ public class ConfigFieldAttribute : Attribute /// /// The tooltip to display when the user hovers a mouse over the label. /// - public string ToolTip { get; } + public string ToolTip { get; set; } /// - /// The group that that config field belongs to (helps to cluster config + /// The group that config field belongs to (helps to cluster config /// fields into meaningful groups). /// public int Group { get; } + public string HelpURL { get; } + /// /// The type of the field - used to inform how to render input controls /// and validation. /// public ConfigFieldType FieldType { get; } + /// + /// Indicates what platforms the config field is valid for. + /// + public PlatformManager.Platform PlatformsEnabledOn { get; } + + public ConfigFieldAttribute( + PlatformManager.Platform enabledOn, + string label, + ConfigFieldType type, + string tooltip = null, + int group = -1, + string helpUrl = null) : this(label, type, tooltip, group, helpUrl) + { + PlatformsEnabledOn = enabledOn; + } + public ConfigFieldAttribute( - string label, - ConfigFieldType type, - string tooltip = null, - int group = -1) + string label, + ConfigFieldType type, + string tooltip = null, + int group = -1, + string helpUrl = null) { + PlatformsEnabledOn = PlatformManager.Platform.Any; + HelpURL = helpUrl; Label = label; ToolTip = tooltip; Group = group; diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldType.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldType.cs index 446c0aa46..ba06784df 100644 --- a/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldType.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldType.cs @@ -60,6 +60,11 @@ public enum ConfigFieldType /// Ulong, + /// + /// A plain float value. + /// + Float, + /// /// A plain double. /// @@ -70,6 +75,42 @@ public enum ConfigFieldType /// TextList, + /// + /// A Guid. + /// + Guid, + + /// + /// The set of Sandbox and Deployment definitions for the project. + /// + ProductionEnvironments, + + /// + /// A set of named client credentials. + /// + SetOfClientCredentials, + + /// + /// A single client credential pair. + /// + ClientCredentials, + + /// + /// A version value. + /// + Version, + + /// + /// Used to render a dropdown whereby a user can select a deployment. + /// + Deployment, + + /// + /// Represents a field that is used to select a value (or values) for an + /// enum. + /// + Enum, + /// /// Indicates that the config has a button that needs rendering. This is /// a ConfigFieldType because a Config can have a field member that is @@ -78,6 +119,12 @@ public enum ConfigFieldType /// arbitrary functionality within a ConfigEditor. /// Button, + + /// + /// Used to render a set of inputs for the InitializeThreadAffinity + /// struct defined within the EOS SDK. + /// + WrappedInitializeThreadAffinity, } } \ No newline at end of file diff --git a/Assets/Plugins/Source/Editor/Platforms/Linux/LinuxConfigEditor.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ExpandFieldAttribute.cs similarity index 70% rename from Assets/Plugins/Source/Editor/Platforms/Linux/LinuxConfigEditor.cs rename to com.playeveryware.eos/Runtime/Core/Config/Attributes/ExpandFieldAttribute.cs index 611315aa7..606eadc18 100644 --- a/Assets/Plugins/Source/Editor/Platforms/Linux/LinuxConfigEditor.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ExpandFieldAttribute.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 PlayEveryWare * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -8,8 +8,8 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -20,9 +20,16 @@ * SOFTWARE. */ -namespace PlayEveryWare.EpicOnlineServices.Editor +namespace PlayEveryWare.EpicOnlineServices { - public class LinuxConfigEditor : PlatformConfigEditor + using System; + + /// + /// This attribute is used to decorate a field member that is a type with field members within. + /// Used when attempting to action on field members of a field member. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ExpandFieldAttribute : Attribute { } } \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/ExpandFieldAttribute.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ExpandFieldAttribute.cs.meta new file mode 100644 index 000000000..ad8080767 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/ExpandFieldAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 734cf7237f44968419842ea72fb80c86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs new file mode 100644 index 000000000..5b0e71335 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + using System.Collections.Generic; + using System.Reflection; + + public struct FieldValidatorFailure + { + public FieldInfo FieldInfo; + public FieldValidatorAttribute FailingAttribute; + public string FailingMessage; + + public FieldValidatorFailure(FieldInfo failingField, + FieldValidatorAttribute failingAttribute, + string failingMessage) + { + FieldInfo = failingField; + FailingAttribute = failingAttribute; + FailingMessage = failingMessage; + } + } + + public class FieldValidator + { + /// + /// Call Field Validator on a single object value. + /// + /// The FieldInfo that contains the attributes. + /// Value of a smallest unit of a class, such as one element of a list. + /// + public static List GetFailingValidatorAttributeOnObject(FieldInfo fieldInfo, object singularValue) + { + List failingValidatorAttributes = new(); + + if (fieldInfo.GetCustomAttribute(typeof(ExpandFieldAttribute)) != null) + { + failingValidatorAttributes.AddRange(GetFailingValidatorAttributeOnClass(singularValue)); + } + else + { + foreach (FieldValidatorAttribute validatorAttribute in fieldInfo.GetCustomAttributes(typeof(FieldValidatorAttribute))) + { + if (!validatorAttribute.FieldValueIsValid(singularValue, out string message)) + { + failingValidatorAttributes.Add(new FieldValidatorFailure(fieldInfo, validatorAttribute, message)); + } + } + } + + return failingValidatorAttributes; + } + + /// + /// Call Field Validator on a field within a class. + /// If the field is a single variable, attempt validation. + /// If the field is a list, it will treat each element individually. + /// + /// + /// The FieldInfo that contains the attributes. + /// Field that is a class or a list of classes should be marked with attribute "Expand". + /// + /// Value of a field within a class. + /// + public static List GetFailingValidatorAttributeOnField(FieldInfo fieldInfo, object fieldValue) + { + List failingValidatorAttributes = new(); + + if (fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(List<>)) + { + List collection = new List((IEnumerable)fieldValue); + + foreach (var element in collection) + { + failingValidatorAttributes.AddRange(GetFailingValidatorAttributeOnObject(fieldInfo, element)); + } + } + else + { + failingValidatorAttributes.AddRange(GetFailingValidatorAttributeOnObject(fieldInfo, fieldValue)); + } + + return failingValidatorAttributes; + } + + /// + /// Call Field Validator on a class. + /// Finds all fields within a class, validates each field. + /// + /// Target class to validate. + /// + public static List GetFailingValidatorAttributeOnClass(object target) + { + List failingValidatorAttributes = new(); + + foreach (FieldInfo curField in target.GetType().GetFields()) + { + failingValidatorAttributes.AddRange(GetFailingValidatorAttributeOnField(curField, curField.GetValue(target))); + } + + return failingValidatorAttributes; + } + + public static bool TryGetFailingValidatorAttributes(object target, out List failingValidatorAttributes) + { + failingValidatorAttributes = new List(); + + failingValidatorAttributes.AddRange(GetFailingValidatorAttributeOnClass(target)); + + return failingValidatorAttributes.Count > 0; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs.meta new file mode 100644 index 000000000..73f66de2a --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9209686f48242544997a71f68e50fa07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Source/Editor/Platforms/Android/AndroidConfigEditor.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs similarity index 73% rename from Assets/Plugins/Source/Editor/Platforms/Android/AndroidConfigEditor.cs rename to com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs index dde19bc2a..bab24907f 100644 --- a/Assets/Plugins/Source/Editor/Platforms/Android/AndroidConfigEditor.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2024 PlayEveryWare * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -8,8 +8,8 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -20,9 +20,15 @@ * SOFTWARE. */ -namespace PlayEveryWare.EpicOnlineServices.Editor +namespace PlayEveryWare.EpicOnlineServices { - public class AndroidConfigEditor : PlatformConfigEditor + using System; + using System.Reflection; + + [AttributeUsage(AttributeTargets.Field)] + + public abstract class FieldValidatorAttribute : Attribute { + public abstract bool FieldValueIsValid(object toValidate, out string configurationProblemMessage); } } \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs.meta new file mode 100644 index 000000000..1806af4aa --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70e4cb59556588240bcc533a7849c5c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs new file mode 100644 index 000000000..f836c4578 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + using System.Reflection; + + [AttributeUsage(AttributeTargets.Field)] + + public class GUIDFieldValidatorAttribute : FieldValidatorAttribute + { + public const string NotAGuidMessage = "The field value could not be parsed into a Guid."; + + public override bool FieldValueIsValid(object toValidate, out string configurationProblemMessage) + { + if (!(toValidate is string stringValue)) + { + configurationProblemMessage = "The field value is not of type string."; + return false; + } + + if (string.IsNullOrEmpty(stringValue)) + { + configurationProblemMessage = "The field value is an empty string."; + return false; + } + + if (!Guid.TryParse(stringValue, out Guid result)) + { + configurationProblemMessage = NotAGuidMessage; + return false; + } + + configurationProblemMessage = string.Empty; + return true; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs.meta new file mode 100644 index 000000000..b1be740c8 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abee8fbb46d0fac49a036589047fcbad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/NonEmptyStringFieldValidatorAttribute.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/NonEmptyStringFieldValidatorAttribute.cs new file mode 100644 index 000000000..cd371781e --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/NonEmptyStringFieldValidatorAttribute.cs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + using System.Reflection; + + [AttributeUsage(AttributeTargets.Field)] + + public class NonEmptyStringFieldValidatorAttribute : FieldValidatorAttribute + { + public const string FieldIsEmptyMessage = "The field value is an empty string."; + + public override bool FieldValueIsValid(object toValidate, out string configurationProblemMessage) + { + if (!(toValidate is string stringValue)) + { + configurationProblemMessage = "The field value is not of type string."; + return false; + } + + if (string.IsNullOrEmpty(stringValue)) + { + configurationProblemMessage = FieldIsEmptyMessage; + return false; + } + + configurationProblemMessage = string.Empty; + return true; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/NonEmptyStringFieldValidatorAttribute.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/NonEmptyStringFieldValidatorAttribute.cs.meta new file mode 100644 index 000000000..7e07cf0cd --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/NonEmptyStringFieldValidatorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6a317d0691bd974db94adbe0610a397 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/PlatformDependentConfigFieldAttribute.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/PlatformDependentConfigFieldAttribute.cs new file mode 100644 index 000000000..d39a088b2 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/PlatformDependentConfigFieldAttribute.cs @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class PlatformDependentConfigFieldAttribute : Attribute + { + /// + /// The label for the config field. + /// + public string Label { get; } + + /// + /// The tooltip to display when the user hovers a mouse over the label. + /// + public string ToolTip { get; } + + /// + /// The group that that config field belongs to (helps to cluster config + /// fields into meaningful groups). + /// + public int Group { get; } + + /// + /// The type of the field - used to inform how to render input controls + /// and validation. + /// + public ConfigFieldType FieldType { get; } + + public PlatformDependentConfigFieldAttribute( + PlatformManager.Platform supportedPlatforms, + string label, + ConfigFieldType type, + string tooltip = null, + int group = -1) + { + Label = label; + ToolTip = tooltip; + Group = group; + FieldType = type; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/PlatformDependentConfigFieldAttribute.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/PlatformDependentConfigFieldAttribute.cs.meta new file mode 100644 index 000000000..165653555 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/PlatformDependentConfigFieldAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 884305d3a04f363438a52c49d7293dbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/SandboxIDFieldValidatorAttribute.cs b/com.playeveryware.eos/Runtime/Core/Config/Attributes/SandboxIDFieldValidatorAttribute.cs new file mode 100644 index 000000000..feccb6902 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/SandboxIDFieldValidatorAttribute.cs @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Text; + using System.Text.RegularExpressions; + + [AttributeUsage(AttributeTargets.Field)] + + public class SandboxIDFieldValidatorAttribute : FieldValidatorAttribute + { + const string PreProductionEnvironmentRegex = @"^p\-[a-zA-Z\d]{30}$"; + public const string FieldDidNotMatchMessage = "The field value is not a GUID, and did not match the regex used for Pre Production Environments: '" + PreProductionEnvironmentRegex + "'."; + + public override bool FieldValueIsValid(object toValidate, out string configurationProblemMessage) + { + if (!(toValidate is string singleStringValue)) + { + configurationProblemMessage = $"The field value is not of type string."; + return false; + } + + if (Regex.IsMatch(singleStringValue, PreProductionEnvironmentRegex)) + { + configurationProblemMessage = string.Empty; + return true; + } + + if (Guid.TryParse(singleStringValue, out _)) + { + configurationProblemMessage = string.Empty; + return true; + } + + configurationProblemMessage = FieldDidNotMatchMessage; + return false; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Attributes/SandboxIDFieldValidatorAttribute.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Attributes/SandboxIDFieldValidatorAttribute.cs.meta new file mode 100644 index 000000000..18acb95e9 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Attributes/SandboxIDFieldValidatorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a321a87822af3449974f3b09d869bfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/Config.cs b/com.playeveryware.eos/Runtime/Core/Config/Config.cs index 6480af92d..c4ee053a5 100644 --- a/com.playeveryware.eos/Runtime/Core/Config/Config.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/Config.cs @@ -8,7 +8,7 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in + * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR @@ -20,13 +20,32 @@ * SOFTWARE. */ +// When compiled outside of Unity - there are some fields within this file +// that are never used. This suppresses those warnings - as the fact that they +// are unused is expected. +#if EXTERNAL_TO_UNITY +// Field is never used +#pragma warning disable CS0169 +// Field is assigned but its value is never used +#pragma warning disable CS0414 +// Field is never assigned to, and will always have its default value. +#pragma warning disable CS0649 +#endif + namespace PlayEveryWare.EpicOnlineServices { + using Common; + using Newtonsoft.Json; using System; using System.Linq; using System.Threading.Tasks; +#if UNITY_EDITOR using UnityEditor; +#endif + +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif using System.Collections.Generic; using System.IO; using System.Reflection; @@ -39,7 +58,6 @@ namespace PlayEveryWare.EpicOnlineServices /// Represents a set of configuration data for use by the EOS Plugin for /// Unity /// - [Serializable] public abstract class Config #if UNITY_EDITOR : ICloneable @@ -83,6 +101,25 @@ public abstract class Config /// private readonly bool _allowDefaultIfFileNotFound; + /// + /// This is the _most recent_, and _current_ version of the JSON schema + /// that is utilized. In this context, "schema" does not mean an actual + /// JSON schema as defined by RFC 8927, but is used to mean, "the + /// version and structure of JSON that this plugin currently writes + /// configuration values in. If anything related to Config changes the + /// format or way it writes JSON, code should be added to migrate the + /// functionality, and this version should be incremented. + /// + private static readonly Version CURRENT_SCHEMA_VERSION = new(1, 0); + + /// + /// Stores the version for the schema used to write the JSON file that + /// this config is backed by. If null, then the file is from before + /// the schemas were being versioned. + /// + [JsonProperty] + private Version schemaVersion; + /// /// Instantiate a new config based on the file at the given filename - /// in a default directory. @@ -97,8 +134,7 @@ public abstract class Config /// protected Config(string filename, bool allowDefault = false) : this(filename, FileSystemUtility.CombinePaths( - Application.streamingAssetsPath, "EOS"), allowDefault) - { } + Application.streamingAssetsPath, "EOS"), allowDefault) { } /// /// Instantiates a new config based on the file at the given file and @@ -125,6 +161,85 @@ protected Config( _allowDefaultIfFileNotFound = allowDefault; } + // This compile conditional is here because async writing is not allowed + // on the Android platform. +#if !UNITY_ANDROID || UNITY_EDITOR + /// + /// Performs migration of the config values and writes the result + /// asynchronously to disk. + /// +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + private async Task MigrateConfigIfNeededAsync() +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + MigrateConfigIfNeededInternal(); +#if UNITY_EDITOR + await WriteAsync(); +#endif + } +#endif + + /// + /// Synchronously migrates the config if it is needed. + /// + private void MigrateConfigIfNeeded() + { + MigrateConfigIfNeededInternal(); +#if UNITY_EDITOR + Write(); +#endif + } + + /// + /// Helper function to perform the components of MigrateConfigIfNeeded + /// functions that are common to both async and non-async contexts. + /// + private void MigrateConfigIfNeededInternal() + { + if (!NeedsMigration()) + { + return; + } + + MigrateConfig(); + } + + /// + /// This function checks to see if the JSON needs to be migrated. + /// + /// + /// True if the config needs to be migrated, false otherwise. + /// + protected virtual bool NeedsMigration() + { + if (schemaVersion == null) + { + return true; + } + + if (VersionUtility.AreVersionsEqual(schemaVersion, CURRENT_SCHEMA_VERSION)) + { + return false; + } + + Debug.LogWarning( + $"Config file with schemaVersion \"{CURRENT_SCHEMA_VERSION}\"" + + " has been read into memory, and needs to be migrated to " + + $"schemaVersion \"{CURRENT_SCHEMA_VERSION}\"."); + + return true; + } + + /// + /// Implement this function in deriving classes to do any additional + /// work on a Config after it has been retrieved and before it is + /// returned by the Get or GetAsync functions. + /// + protected virtual void MigrateConfig() + { + // Default implementation is to do nothing. + } + /// /// Allows deriving classes to register their constructor method in /// order to enforce the factory pattern. This requires that each class @@ -208,6 +323,15 @@ public static async Task GetAsync() where T : Config // Use the factory method to create the config. T instance = (T)factory(); + // This compile conditional is here because write should only happen + // within the unity editor context. +#if UNITY_EDITOR + if (!await FileSystemUtility.FileExistsAsync(instance.FilePath) && instance._allowDefaultIfFileNotFound) + { + await instance.WriteAsync(); + } +#endif + // Asynchronously read config values from the corresponding file. await instance.ReadAsync(); @@ -216,6 +340,8 @@ public static async Task GetAsync() where T : Config s_cachedConfigs.Add(typeof(T), instance); #endif + await instance.MigrateConfigIfNeededAsync(); + // Return the config being retrieved. return instance; } @@ -245,6 +371,15 @@ public static T Get() where T : Config // Use the factory method to create the config. T instance = (T)factory(); +// This compile conditional is here because write should only happen +// within the unity editor context. +#if UNITY_EDITOR + if (!FileSystemUtility.FileExists(instance.FilePath) && instance._allowDefaultIfFileNotFound) + { + instance.Write(); + } +#endif + // Synchronously read config values from the corresponding file. instance.Read(); @@ -253,6 +388,8 @@ public static T Get() where T : Config s_cachedConfigs.Add(typeof(T), instance); #endif + instance.MigrateConfigIfNeeded(); + // Return the config being retrieved. return instance; } @@ -261,6 +398,7 @@ public static T Get() where T : Config /// Returns the fully-qualified path to the file that holds the /// configuration values. /// + [JsonIgnore] public string FilePath { get @@ -328,54 +466,6 @@ private async Task EnsureConfigFileExistsAsync() } } - /// - /// This delegate describes the signature of a function that can be used - /// to convert a list of strings into a single enum value. - /// - /// - /// The type of enum to convert the list of strings to a value of. - /// - /// - /// Strings to convert into an enum value. - /// - /// - /// The enum value that results from performing a bitwise OR operation - /// on the list of enum values that result from converting each item in - /// the provided list of strings to an enum value of the indicated type. - /// - /// - /// True if the parsing of flags was successful, false otherwise. - /// - protected delegate bool TryParseEnumDelegate(IList - stringFlags, out TEnum result) where TEnum : struct, Enum; - - /// - /// Private static wrapper to handle converting a list of strings into - /// a single enum value. - /// - /// - /// The type of enum to convert the list of strings into. - /// - /// - /// The list of strings to convert into a single enum value. - /// - /// - /// The function used to convert the list of strings into a single enum - /// value. - /// - /// A single enum value that is the result of a bitwise OR - /// operation between the enum values that result from parsing each of - /// the items in a list into the indicated enum type value. - /// - protected static TEnum StringsToEnum( - IList stringFlags, - TryParseEnumDelegate parseEnumFn) - where TEnum : struct, Enum - { - _ = parseEnumFn(stringFlags, out TEnum result); - return result; - } - // Functions declared below should only ever be utilized in the editor. // They are so divided to guarantee separation of concerns. #if UNITY_EDITOR @@ -386,14 +476,12 @@ protected static TEnum StringsToEnum( /// /// Whether to output "pretty" JSON to the file. /// - /// - /// Indicates whether to update the asset database after writing. - /// /// Task - public virtual async Task WriteAsync( - bool prettyPrint = true, - bool updateAssetDatabase = true) + public virtual async Task WriteAsync(bool prettyPrint = true) { + // Set the schema version to the current before writing. + schemaVersion = CURRENT_SCHEMA_VERSION; + var json = JsonUtility.ToJson(this, prettyPrint); // If the json hasn't changed since it was last read, then @@ -410,13 +498,11 @@ public virtual async Task WriteAsync( /// /// Whether to output "pretty" JSON to the file. /// - /// - /// Indicates whether to update the asset database after writing. - /// - public virtual void Write( - bool prettyPrint = true, - bool updateAssetDatabase = true) + public virtual void Write(bool prettyPrint = true) { + // Set the schema version to the current before writing. + schemaVersion = CURRENT_SCHEMA_VERSION; + var json = JsonUtility.ToJson(this, prettyPrint); // If the json hasn't changed since it was last read, then @@ -425,12 +511,6 @@ public virtual void Write( return; FileSystemUtility.WriteFile(FilePath, json); - - if (updateAssetDatabase) - { - AssetDatabase.SaveAssets(); - AssetDatabase.Refresh(); - } } /// @@ -689,5 +769,16 @@ private static IEnumerable IteratePropertiesAndFields( #endif } - -} \ No newline at end of file +} + +// When compiled outside of Unity - there are some fields within this file +// that are never used. This suppresses those warnings - as the fact that they +// are unused is expected. +#if EXTERNAL_TO_UNITY +// Field is never used +#pragma warning restore CS0169 +// Field is assigned but its value is never used +#pragma warning restore CS0414 +// Field is never assigned to, and will always have its default value. +#pragma warning restore CS0649 +#endif \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/Deployment.cs b/com.playeveryware.eos/Runtime/Core/Config/Deployment.cs new file mode 100644 index 000000000..093c54ba9 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Deployment.cs @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using PlayEveryWare.EpicOnlineServices.Utility; + using System; + + public struct Deployment : IEquatable + { + public SandboxId SandboxId; + + public Guid DeploymentId; + + public bool Equals(Deployment other) + { + return SandboxId.Equals(other.SandboxId) && DeploymentId.Equals(other.DeploymentId); + } + + public override bool Equals(object obj) + { + return obj is Deployment deployment && Equals(deployment); + } + + public override int GetHashCode() + { + return HashUtility.Combine(SandboxId, DeploymentId); + } + } + +} diff --git a/com.playeveryware.eos/Runtime/Core/Config/Deployment.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/Deployment.cs.meta new file mode 100644 index 000000000..0728bdf09 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/Deployment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bed064434e1a5ff478c7794dca812e99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/EOSConfig.cs b/com.playeveryware.eos/Runtime/Core/Config/EOSConfig.cs index a856b2d69..79cd42fcd 100644 --- a/com.playeveryware.eos/Runtime/Core/Config/EOSConfig.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/EOSConfig.cs @@ -1,24 +1,31 @@ /* -* Copyright (c) 2021 PlayEveryWare -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// When compiled outside of Unity - there are some fields within this file +// that are never used. This suppresses those warnings - as the fact that they +// are unused is expected. +#if EXTERNAL_TO_UNITY +#pragma warning disable CS0169 // Disable unused field warnings +#endif namespace PlayEveryWare.EpicOnlineServices { @@ -33,28 +40,30 @@ namespace PlayEveryWare.EpicOnlineServices #endif using System; using System.Collections.Generic; +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif using System.Text.RegularExpressions; - using Extensions; - + using Newtonsoft.Json; using PlayEveryWare.EpicOnlineServices.Utility; /// /// Represents the default deployment ID to use when a given sandbox ID is /// active. /// - [Serializable] public class SandboxDeploymentOverride { + [SandboxIDFieldValidator] public string sandboxID; + + [GUIDFieldValidator] public string deploymentID; } /// /// Represents the EOS Configuration used for initializing EOS SDK. /// - [Serializable] - [ConfigGroup("EOS Config", new [] + [ConfigGroup("EOS Config", new[] { "Product Information", "Deployment", @@ -67,13 +76,25 @@ public class EOSConfig : Config { static EOSConfig() { - InvalidEncryptionKeyRegex = new Regex("[^0-9a-fA-F]"); RegisterFactory(() => new EOSConfig()); } - protected EOSConfig() : base("EpicOnlineServicesConfig.json") + protected EOSConfig() : base("EpicOnlineServicesConfig.json") { } + protected override bool NeedsMigration() + { + // Because EOSConfig is becoming obsolete, it should never + // be "migrated", but left-as-is. + // + // To be clear - migration _from_ EOSConfig *does* take place, but + // it is considered in those cases that the new config classes are + // the ones being migrated - the EOSConfig _itself_ is therefore + // never migrated. Non-obsolete code makes exclusive use of the new + // classes. + return false; + } + #region Product Information /// @@ -82,6 +103,7 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// [ConfigField("Product Name", ConfigFieldType.Text, "Product name defined in the Development Portal.", 0)] + [NonEmptyStringFieldValidator] public string productName; /// @@ -89,6 +111,7 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// [ConfigField("Product Version", ConfigFieldType.Text, "Version of the product.", 0)] + [NonEmptyStringFieldValidator] public string productVersion; /// @@ -97,6 +120,7 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// [ConfigField("Product Id", ConfigFieldType.Text, "Product Id defined in the Development Portal.", 0)] + [GUIDFieldValidator] public string productID; #endregion @@ -107,8 +131,9 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// Sandbox Id defined in the /// [Development Portal](https://dev.epicgames.com/portal/) /// - [ConfigField("Sandbox Id", ConfigFieldType.Text, + [ConfigField("Sandbox Id", ConfigFieldType.Text, "Sandbox Id to use.", 1)] + [SandboxIDFieldValidator] public string sandboxID; /// @@ -117,24 +142,26 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// [ConfigField("Deployment Id", ConfigFieldType.Text, "Deployment Id to use.", 1)] + [GUIDFieldValidator] public string deploymentID; /// /// SandboxDeploymentOverride pairs used to override Deployment ID when /// a given Sandbox ID is used. /// - [ConfigField("Sandbox Deployment Overrides", - ConfigFieldType.TextList, + [ConfigField("Sandbox Deployment Overrides", + ConfigFieldType.TextList, "Deployment Id to use.", 1)] + [ExpandField] public List sandboxDeploymentOverrides; /// /// Set to 'true' if the application is a dedicated game server. /// - [ConfigField("Is Server", - ConfigFieldType.Flag, + [ConfigField("Is Server", + ConfigFieldType.Flag, "Indicates whether the application is a dedicated game " + - "server.", + "server.", 1)] public bool isServer; @@ -147,7 +174,7 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// [Development Portal](https://dev.epicgames.com/portal/) /// [ConfigField("Client Secret", ConfigFieldType.Text, - "Client Secret defined in the Development Portal.", + "Client Secret defined in the Development Portal.", 2)] public string clientSecret; @@ -156,7 +183,7 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// [Development Portal](https://dev.epicgames.com/portal/) /// [ConfigField("Client Id", ConfigFieldType.Text, - "Client Id defined in the Development Portal.", + "Client Id defined in the Development Portal.", 2)] public string clientID; @@ -174,7 +201,7 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// pressed. /// [ConfigField("Generate Key", ConfigFieldType.Button, - "Click to generate an encryption key.", + "Click to generate an encryption key.", 2)] private Action GenerateKeyButtonAction; @@ -182,23 +209,27 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") #region Flags +#if !EOS_DISABLE /// /// Flags; used to initialize the EOS platform. /// - [ConfigField("Platform Options", + [ConfigField("Platform Options", ConfigFieldType.TextList, - "Platform option flags", + "Platform option flags", 3)] - public List platformOptionsFlags; + [JsonConverter(typeof(ListOfStringsToPlatformFlags))] + public WrappedPlatformFlags platformOptionsFlags; /// /// Flags; used to set user auth when logging in. /// - [ConfigField("Auth Scope Options", - ConfigFieldType.TextList, - "Platform option flags", + [ConfigField("Auth Scope Options", + ConfigFieldType.TextList, + "Platform option flags", 3)] - public List authScopeOptionsFlags; + [JsonConverter(typeof(ListOfStringsToAuthScopeFlags))] + public AuthScopeFlags authScopeOptionsFlags; +#endif #endregion @@ -208,10 +239,10 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// Tick Budget; used to define the maximum amount of execution time the /// EOS SDK can use each frame. /// - [ConfigField("Tick Budget (ms)", - ConfigFieldType.Uint, + [ConfigField("Tick Budget (ms)", + ConfigFieldType.Uint, "Used to define the maximum amount of execution time the " + - "EOS SDK can use each frame.", + "EOS SDK can use each frame.", 3)] public uint tickBudgetInMilliseconds; @@ -226,8 +257,8 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// . /// /// - [ConfigField("Network Timeout Seconds", - ConfigFieldType.Double, + [ConfigField("Network Timeout Seconds", + ConfigFieldType.Double, "Indicates the maximum number of seconds that EOS SDK " + "will allow network calls to run before failing with EOS_TimedOut.", 3)] @@ -237,59 +268,65 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// Network Work Affinity; specifies thread affinity for network /// management that is not IO. /// - [ConfigField("Network Work", ConfigFieldType.Text, + [ConfigField("Network Work", ConfigFieldType.Ulong, "Specifies affinity for threads that manage network tasks " + - "that are not IO related.", + "that are not IO related.", 3)] - public string ThreadAffinity_networkWork; + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_networkWork; /// /// Storage IO Affinity; specifies affinity for threads that will /// interact with a storage device. /// - [ConfigField("Storage IO", ConfigFieldType.Text, - "Specifies affinity for threads that generate storage IO.", + [ConfigField("Storage IO", ConfigFieldType.Ulong, + "Specifies affinity for threads that generate storage IO.", 3)] - public string ThreadAffinity_storageIO; + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_storageIO; /// /// Web Socket IO Affinity; specifies affinity for threads that generate /// web socket IO. /// - [ConfigField("Web Socket IO", ConfigFieldType.Text, + [ConfigField("Web Socket IO", ConfigFieldType.Ulong, "Specifies affinity for threads that generate web socket " + "IO.", 3)] - public string ThreadAffinity_webSocketIO; + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_webSocketIO; /// /// P2P IO Affinity; specifies affinity for any thread that will /// generate IO related to P2P traffic and management. /// - [ConfigField("P2P IO", ConfigFieldType.Text, + [ConfigField("P2P IO", ConfigFieldType.Ulong, "Specifies affinity for any thread that will generate IO " + - "related to P2P traffic and management.", + "related to P2P traffic and management.", 3)] - public string ThreadAffinity_P2PIO; + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_P2PIO; /// /// HTTP Request IO Affinity; specifies affinity for any thread that /// will generate http request IO. /// - [ConfigField("HTTP Request IO", ConfigFieldType.Text, + [ConfigField("HTTP Request IO", ConfigFieldType.Ulong, "Specifies the affinity for any thread that will generate " + - "HTTP request IO.", + "HTTP request IO.", 3)] - public string ThreadAffinity_HTTPRequestIO; + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_HTTPRequestIO; /// /// RTC IO Affinity</c> specifies affinity for any thread that /// will generate IO related to RTC traffic and management. /// - [ConfigField("RTC IO", ConfigFieldType.Text, + [ConfigField("RTC IO", ConfigFieldType.Ulong, "Specifies the affinity for any thread that will generate " + - "IO related to RTC traffic and management.", + "IO related to RTC traffic and management.", 3)] - public string ThreadAffinity_RTCIO; + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_RTCIO; #endregion @@ -301,8 +338,8 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// handle showing the overlay. This doesn't always mean input makes it /// to the EOS SDK. /// - [ConfigField("Always Send Input to Overlay", - ConfigFieldType.Flag, + [ConfigField("Always Send Input to Overlay", + ConfigFieldType.Flag, "If true, the plugin will always send input to the " + "overlay from the C# side to native, and handle showing the " + "overlay. This doesn't always mean input makes it to the EOS SDK.", @@ -312,10 +349,11 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// /// Initial Button Delay. /// - [ConfigField("Initial Button Delay", ConfigFieldType.Text, + [ConfigField("Initial Button Delay", ConfigFieldType.Float, "Initial Button Delay (if not set, whatever the default " + "is will be used).", 4)] - public string initialButtonDelayForOverlay; + [JsonConverter(typeof(StringToTypeConverter))] + public float? initialButtonDelayForOverlay; /// /// Repeat button delay for overlay. @@ -323,21 +361,12 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") [ConfigField("Repeat Button Delay", ConfigFieldType.Text, "Repeat button delay for the overlay. If not set, " + "whatever the default is will be used.", 4)] - public string repeatButtonDelayForOverlay; - - /// - /// HACK: send force send input without delay</c>If true, the - /// native plugin will always send input received directly to the SDK. - /// If set to false, the plugin will attempt to delay the input to - /// mitigate CPU spikes caused by spamming the SDK. - /// - [ConfigField("Send input without delay", - ConfigFieldType.Flag, - "Workaround to force send input without any delay. If " + - "true, the native plugin will always send input receive directly " + - "to the SDK.", 4)] - public bool hackForceSendInputDirectlyToSDK; + [JsonConverter(typeof(StringToTypeConverter))] + public float? repeatButtonDelayForOverlay; + // This compile conditional is here so that when EOS is disabled, in the + // Epic namespace is referenced. +#if !EOS_DISABLE /// /// When this combination of buttons is pressed on a controller, the /// social overlay will toggle on. @@ -345,50 +374,38 @@ protected EOSConfig() : base("EpicOnlineServicesConfig.json") /// use that value if this configuration field is null, empty, or contains /// only . /// - public List toggleFriendsButtonCombination = new List() { -#if !EOS_DISABLE - InputStateButtonFlags.SpecialLeft.ToString() + [JsonConverter(typeof(ListOfStringsToInputStateButtonFlags))] + public InputStateButtonFlags toggleFriendsButtonCombination = InputStateButtonFlags.SpecialLeft; #endif - }; - -#endregion - public static Regex InvalidEncryptionKeyRegex; - - private static bool IsEncryptionKeyValid(string key) - { - return - //key not null - key != null && - //key is 64 characters - key.Length == 64 && - //key is all hex characters - !InvalidEncryptionKeyRegex.Match(key).Success; - } + #endregion /// /// Override the default sandbox and deployment id. Uses the sandboxId /// as a key to determine the corresponding deploymentId that has been /// set by the user in the configuration window. /// - /// The sandbox id to use. - public void SetDeployment(string sandboxId) + /// The sandbox id to use. + public void SetDeployment(string launcherSandboxId) { // Confirm that the sandboxId is stored in the list of overrides - if (!TryGetDeployment(sandboxDeploymentOverrides, sandboxId, + if (TryGetDeployment(sandboxDeploymentOverrides, launcherSandboxId, out SandboxDeploymentOverride overridePair)) { - Debug.LogError($"The given sandboxId \"{sandboxId}\" could not be found in the configured list of deployment override values."); - return; - } - - Debug.Log($"Sandbox ID overridden to: \"{overridePair.sandboxID}\"."); - Debug.Log($"Deployment ID overridden to: \"{overridePair.deploymentID}\"."); - - // Override the sandbox and deployment Ids - sandboxID = overridePair.sandboxID; - deploymentID = overridePair.deploymentID; + Debug.Log($"Sandbox ID overridden to: \"{overridePair.sandboxID}\"."); + Debug.Log($"Deployment ID overridden to: \"{overridePair.deploymentID}\"."); + // Override the sandbox and deployment Ids + sandboxID = overridePair.sandboxID; + deploymentID = overridePair.deploymentID; + } + else + { + if (sandboxID != launcherSandboxId) + { + throw new Exception($"The launcher sandboxId \"{launcherSandboxId}\" does not have a corresponding deploymentId configured."); + } + } // TODO: This will trigger a need to re-validate the config values } @@ -431,45 +448,6 @@ private static bool TryGetDeployment( #if !EOS_DISABLE - /// - /// Returns a single PlatformFlags enum value that results from a - /// bitwise OR operation of all the platformOptionsFlags flags on this - /// config. - /// - /// A PlatformFlags enum value. - public PlatformFlags GetPlatformFlags() - { - return StringsToEnum( - platformOptionsFlags, - PlatformFlagsExtensions.TryParse); - } - - /// - /// Returns a single AuthScopeFlags enum value that results from a - /// bitwise OR operation of all the authScopeOptionsFlags flags on this - /// config. - /// - /// An AuthScopeFlags enum value. - public AuthScopeFlags GetAuthScopeFlags() - { - return StringsToEnum( - authScopeOptionsFlags, - AuthScopeFlagsExtensions.TryParse); - } - - /// - /// Returns a single InputStateButtonFlags enum value that results from a - /// bitwise OR operation of all the flags on this - /// config. - /// - /// An InputStateButtonFlags enum value. - public InputStateButtonFlags GetToggleFriendsButtonCombinationFlags() - { - return StringsToEnum( - toggleFriendsButtonCombination, - (IList stringFlags, out InputStateButtonFlags result) => EnumUtility.TryParse(stringFlags, null, out result)); - } - /// /// Given a reference to an InitializeThreadAffinity struct, set the /// member fields contained within to match the values of this config. @@ -479,24 +457,43 @@ public InputStateButtonFlags GetToggleFriendsButtonCombinationFlags() /// public void ConfigureOverrideThreadAffinity(ref InitializeThreadAffinity affinity) { - affinity.NetworkWork = ThreadAffinity_networkWork.ToUlong(); - affinity.StorageIo = ThreadAffinity_storageIO.ToUlong(); - affinity.WebSocketIo = ThreadAffinity_webSocketIO.ToUlong(); - affinity.P2PIo = ThreadAffinity_P2PIO.ToUlong(); - affinity.HttpRequestIo = ThreadAffinity_HTTPRequestIO.ToUlong(); - affinity.RTCIo = ThreadAffinity_RTCIO.ToUlong(); - } -#endif + if (ThreadAffinity_HTTPRequestIO.HasValue) + { + affinity.HttpRequestIo = ThreadAffinity_HTTPRequestIO.Value; + } - /// - /// Determines whether the encryption key for the config is valid. - /// - /// - /// True if the encryption key is valid, false otherwise. - /// - public bool IsEncryptionKeyValid() - { - return IsEncryptionKeyValid(encryptionKey); + if (ThreadAffinity_P2PIO.HasValue) + { + affinity.P2PIo = ThreadAffinity_P2PIO.Value; + } + + if (ThreadAffinity_RTCIO.HasValue) + { + affinity.RTCIo = ThreadAffinity_RTCIO.Value; + } + + if (ThreadAffinity_networkWork.HasValue) + { + affinity.NetworkWork = ThreadAffinity_networkWork.Value; + } + + if (ThreadAffinity_storageIO.HasValue) + { + affinity.StorageIo = ThreadAffinity_storageIO.Value; + } + + if (ThreadAffinity_webSocketIO.HasValue) + { + affinity.WebSocketIo = ThreadAffinity_webSocketIO.Value; + } } +#endif } } + +// When compiled outside of Unity - there are some fields within this file +// that are never used. This suppresses those warnings - as the fact that they +// are unused is expected. +#if EXTERNAL_TO_UNITY +#pragma warning restore CS0169 // Disable unused field warnings +#endif \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter.meta b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter.meta new file mode 100644 index 000000000..f9b6d9288 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6051d771c0e1bf4b83e67b7b1656089 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/GuidConverter.cs b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/GuidConverter.cs new file mode 100644 index 000000000..1feb2c046 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/GuidConverter.cs @@ -0,0 +1,70 @@ +/* +* Copyright (c) 2024 PlayEveryWare +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +namespace PlayEveryWare.EpicOnlineServices +{ + using Newtonsoft.Json; + using System; + + public class GuidConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is Guid guid) + { + // Serialize Guid as a lowercase string without dashes + writer.WriteValue(guid.ToString("N").ToLowerInvariant()); + } + else + { + writer.WriteNull(); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + var stringValue = reader.Value?.ToString(); + + if (string.IsNullOrEmpty(stringValue)) + { + return objectType == typeof(Guid?) ? (Guid?)null : Guid.Empty; + } + + if (!Guid.TryParseExact(stringValue, "N", out Guid value)) + { + if (!Guid.TryParse(stringValue, out value)) + { + value = Guid.Empty; + } + } + + // Deserialize the Guid from the string + return value; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Guid) || objectType == typeof(Guid?); + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/GuidConverter.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/GuidConverter.cs.meta new file mode 100644 index 000000000..fa51d0700 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/GuidConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b9c908405aa73945ade2d57fd18f1a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/ListOfStringsToEnumConverters.cs b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/ListOfStringsToEnumConverters.cs new file mode 100644 index 000000000..a844d1431 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/ListOfStringsToEnumConverters.cs @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + // This compile conditional is here so that when EOS is disabled, nothing is + // referenced in the Epic namespace. +#if !EOS_DISABLE + using Epic.OnlineServices.Auth; + using Epic.OnlineServices.IntegratedPlatform; + using Epic.OnlineServices.Platform; + using Epic.OnlineServices.UI; + using Extensions; +#endif + + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using System; + using System.Collections.Generic; + using Utility; + + // This compile conditional is here so that when EOS is disabled, nothing is + // referenced in the Epic namespace. +#if !EOS_DISABLE + + /// + /// Used to allow conversion from either a list of strings, or an enum's + /// "ToString()" function of strings to a single enum value. + /// + internal class + ListOfStringsToIntegratedPlatformManagementFlags : ListOfStringsToEnumConverter< + IntegratedPlatformManagementFlags> + { + protected override IntegratedPlatformManagementFlags FromStringArray(JArray array) + { + return FromStringArrayWithCustomMapping(array, IntegratedPlatformManagementFlagsExtensions.CustomMappings); + } + } + + /// + /// Used to allow conversion from either a list of strings, or an enum's + /// "ToString()" function of strings to a single enum value. + /// + internal class ListOfStringsToInputStateButtonFlags : ListOfStringsToEnumConverter + { + protected override InputStateButtonFlags FromStringArray(JArray array) + { + return FromStringArrayWithCustomMapping(array, null); + } + } + + /// + /// Used to allow conversion from either a list of strings, or an enum's + /// "ToString()" function of strings to a single enum value. + /// + internal class ListOfStringsToAuthScopeFlags : ListOfStringsToEnumConverter + { + protected override AuthScopeFlags FromStringArray(JArray array) + { + return FromStringArrayWithCustomMapping(array, AuthScopeFlagsExtensions.CustomMappings); + } + } + + /// + /// Used to allow conversion from either a list of strings, or an enum's + /// "ToString()" function of strings to a single enum value. + /// + internal class ListOfStringsToPlatformFlags : ListOfStringsToEnumConverter + { + protected override WrappedPlatformFlags FromStringArray(JArray array) + { + Dictionary wrappedCustomMapping = new(); + + foreach (var customMapping in PlatformFlagsExtensions.CustomMappings) + { + string key = customMapping.Key; + PlatformFlags value = customMapping.Value; + wrappedCustomMapping[key] = value.Wrap(); + } + + return FromStringArrayWithCustomMapping(array, wrappedCustomMapping); + } + } +#endif + + /// + /// This class provides base functionality and declares an interface for + /// converter classes that can accept either a JSON array of strings, a + /// single value, or a single string into an enum value of the type + /// specified by the type parameter. + /// + /// + /// Indicates the enum type that conversion functionality is being + /// implemented for. + /// + internal abstract class ListOfStringsToEnumConverter : JsonConverter where TEnum : struct, Enum + { + /// + /// Used as a cleaner proxy for typeof(T) + /// + private readonly Type _targetType = typeof(TEnum); + + /// + /// Determines whether the type that the implementing class attribute is + /// applied to can be converted by this JsonConverter. + /// + /// + /// The object to convert _to_. + /// + /// + /// True if the conversion is supported, false otherwise. + /// + public override bool CanConvert(Type objectType) + { + return typeof(TEnum).IsEnum; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var token = JToken.Load(reader); + + return token.Type switch + { + JTokenType.Array => FromStringArray(token as JArray), + + JTokenType.String => Enum.Parse( + _targetType, + token.Value()), + + JTokenType.Integer => FromNumberValue(token), + + JTokenType.Null => default(TEnum), + + _ => throw new JsonSerializationException( + $"Unexpected token type '{token.Type}' when " + + $"parsing to type '{_targetType}'."), + }; + } + + /// + /// Implementing classes should override this function to define the + /// behavior when an array of strings is being converted into a single + /// enum value. + /// + /// + /// A list of string values from which to compose a single enum value. + /// + /// A single enum value of type T. + protected abstract TEnum FromStringArray(JArray array); + + /// + /// Implementing classes should override this function to define the + /// behavior when a single numeric value is given for the enum value. + /// This is important for each implementing class to define because + /// each enum may have a different underlying numeric type. + /// + /// + /// The token containing the number value. + /// + /// + /// A single enum value of type T. + /// + protected TEnum FromNumberValue(JToken token) + { + Type underlyingType = Enum.GetUnderlyingType(typeof(TEnum)); + object value = Convert.ChangeType(token, underlyingType); + + object lowestValue = EnumUtility.GetLowest(); + + if (Comparer.Default.Compare(value, lowestValue) < 0) + { + value = lowestValue; + } + + return (TEnum)value; + } + + /// + /// Given a JSON array and a custom mapping lookup dictionary, this + /// function will combose a single enum value based on the values + /// provided. + /// + /// + /// A JSON array of string values. + /// + /// + /// Any custom mappings that should be used to parse the enum value. If + /// there are no such custom mappings, this value can be null. + /// + /// + /// A single enum value of type T. + /// + protected TEnum FromStringArrayWithCustomMapping(JArray array, IDictionary customMappings) + { + List elementsAsStrings = new(); + foreach (JToken element in array) + { + elementsAsStrings.Add(element.ToString()); + } + + _ = EnumUtility.TryParse(elementsAsStrings, customMappings, out TEnum result); + + return result; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // Serialize the value using the default serializer + serializer.Serialize(writer, value); + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/ListOfStringsToEnumConverters.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/ListOfStringsToEnumConverters.cs.meta new file mode 100644 index 000000000..7613e50ba --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/ListOfStringsToEnumConverters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c185438a106e7544d9cfcfe70d6891dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/StringToTypeConverter.cs b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/StringToTypeConverter.cs new file mode 100644 index 000000000..1ee338041 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/StringToTypeConverter.cs @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using System; + using System.ComponentModel; + + /// + /// Used to allow the automatic conversion to an indicated type from either + /// that type's default method of serialization, or from a string + /// representation of that value. + /// + /// + /// The type that is being converted to. + /// + public class StringToTypeConverter : JsonConverter + { + /// + /// Proxy for typeof(T) + /// + private readonly Type _targetType = typeof(T); + + /// + /// If the type parameter is a nullable, this allows inspection of what + /// the encapsulated type is. + /// + private readonly Type _underlyingType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + + /// + /// Indicates whether the value can be converted to by this + /// JsonConverter. + /// + /// + /// The type to convert to. + /// + /// + /// True if the converter can produce the given type, false otherwise. + /// + public override bool CanConvert(Type objectType) + { + return _targetType.IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var token = JToken.Load(reader); + + // Handle null tokens for nullable types + if (token.Type == JTokenType.Null && Nullable.GetUnderlyingType(objectType) != null) + { + return null; + } + + try + { + // Convert the token to the target type + return ConvertToken(token, _underlyingType); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error converting token '{token}' to type '{_targetType}'.", ex); + } + } + + /// + /// Helper function to actually accomplish the conversion from a given + /// JSON token into the given type. + /// + /// The JSON data to convert from. + /// The type to convert to. + /// + /// An instance of the given type - with values determined by the + /// provided JSON data. + /// + /// + /// Thrown if there is an error in the JSON serialization. + /// + private static object ConvertToken(JToken token, Type targetType) + { + string stringValue = token.Value(); + if (string.IsNullOrEmpty(stringValue)) + { + stringValue = default(T).ToString(); + } + + switch (token.Type) + { + case JTokenType.String: + if (targetType.IsEnum) + { + return Enum.Parse(targetType, stringValue); + } + + if (targetType == typeof(Guid)) + { + return Guid.Parse(stringValue); + } + else + { + // Use TypeConverter for other types + var converter = TypeDescriptor.GetConverter(targetType); + return converter.ConvertFromInvariantString(stringValue); + } + case JTokenType.Integer: + case JTokenType.Float: + // Directly convert numeric types + return token.ToObject(targetType); + case JTokenType.Guid when targetType == typeof(Guid): + return token.ToObject(); + case JTokenType.None: + case JTokenType.Object: + case JTokenType.Array: + case JTokenType.Constructor: + case JTokenType.Property: + case JTokenType.Comment: + case JTokenType.Boolean: + case JTokenType.Null: + case JTokenType.Undefined: + case JTokenType.Date: + case JTokenType.Raw: + case JTokenType.Bytes: + case JTokenType.Uri: + case JTokenType.TimeSpan: + default: + throw new JsonSerializationException($"Unexpected token type '{token.Type}' when parsing to type '{targetType}'."); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // Serialize the value using the default serializer + serializer.Serialize(writer, value); + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/StringToTypeConverter.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/StringToTypeConverter.cs.meta new file mode 100644 index 000000000..4e5f74ffc --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/JsonConverter/StringToTypeConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fa92f0297849bf4c93d107affda26e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/LogLevelConfig.cs b/com.playeveryware.eos/Runtime/Core/Config/LogLevelConfig.cs index 68ce31535..ba6d13fd1 100644 --- a/com.playeveryware.eos/Runtime/Core/Config/LogLevelConfig.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/LogLevelConfig.cs @@ -25,7 +25,6 @@ namespace PlayEveryWare.EpicOnlineServices { - [Serializable] public class LogCategoryLevelPair { public string Category; @@ -38,15 +37,14 @@ public LogCategoryLevelPair(string category, string level) } } - [Serializable] public class LogLevelConfig : Config { static LogLevelConfig() { RegisterFactory(() => new LogLevelConfig()); } - public LogLevelConfig() : base("log_level_config.json") { } + protected LogLevelConfig() : base("log_level_config.json") { } public List LogCategoryLevelPairs; } -} \ No newline at end of file +} diff --git a/com.playeveryware.eos/Runtime/Core/Config/PlatformConfig.cs b/com.playeveryware.eos/Runtime/Core/Config/PlatformConfig.cs index 726e034e2..422254e74 100644 --- a/com.playeveryware.eos/Runtime/Core/Config/PlatformConfig.cs +++ b/com.playeveryware.eos/Runtime/Core/Config/PlatformConfig.cs @@ -8,8 +8,8 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -22,24 +22,40 @@ namespace PlayEveryWare.EpicOnlineServices { + // This compile conditional is here so that when EOS is disabled, nothing is + // referenced in the Epic namespace. #if !EOS_DISABLE using Epic.OnlineServices.IntegratedPlatform; - using Utility; + using Epic.OnlineServices.Auth; + using Epic.OnlineServices.Platform; + using Epic.OnlineServices.UI; #endif - using Extensions; + using Common; + using Common.Extensions; + using Newtonsoft.Json; using System; - using System.Collections.Generic; + +#if !EXTERNAL_TO_UNITY + using UnityEngine; +#endif + + using Utility; /// /// Represents a set of configuration data for use by the EOS Plugin for /// Unity on a specific platform. /// - [Serializable] public abstract class PlatformConfig : Config { + private const PlatformManager.Platform OVERLAY_COMPATIBLE_PLATFORMS = ~(PlatformManager.Platform.Android | + PlatformManager.Platform.iOS | + PlatformManager.Platform.macOS | + PlatformManager.Platform.Linux); + /// /// The platform that the set of configuration data is to be applied on. /// + [JsonIgnore] public PlatformManager.Platform Platform { get; } /// @@ -50,12 +66,191 @@ public abstract class PlatformConfig : Config /// it's implementation, but the intent is documented here for the sake /// of clarity and context. /// + [JsonProperty] // Allow deserialization + [JsonIgnore] // Disallow serialization + [Obsolete] // Mark as obsolete so that warnings are generated + // whenever this is utilized (in the code that does the + // migration those warnings will be suppressed). public EOSConfig overrideValues; + #region Deployment + + [ConfigField("Deployment", ConfigFieldType.Deployment, "Select the deployment to use.", 1)] + public Deployment deployment; + +#if !EOS_DISABLE + [ConfigField("Client Credentials", ConfigFieldType.ClientCredentials, "Select client credentials to use.", 1)] + public EOSClientCredentials clientCredentials; +#endif + + [ConfigField("Is Server", ConfigFieldType.Flag, "Check this if your game is a dedicated game server.", 1)] + public bool isServer; + +#endregion + + #region Flags + + // This conditional is here because if EOS_DISABLE is defined, then + // the field member types will not be available. +#if !EOS_DISABLE + /// + /// Flags; used to initialize the EOS platform. + /// + [ConfigField("Platform Flags", + ConfigFieldType.Enum, + "Platform option flags", + 2, "https://dev.epicgames.com/docs/epic-online-services/eos-get-started/working-with-the-eos-sdk/eos-overlay-overview#eos-platform-flags-for-the-eos-overlay")] + [JsonConverter(typeof(ListOfStringsToPlatformFlags))] + public WrappedPlatformFlags platformOptionsFlags; + + /// + /// Flags; used to set user auth when logging in. + /// + [ConfigField("Auth Scope Flags", + ConfigFieldType.Enum, + "Platform option flags", + 2, "https://dev.epicgames.com/docs/api-ref/enums/eos-e-auth-scope-flags?lang=en-US")] + [JsonConverter(typeof(ListOfStringsToAuthScopeFlags))] + public AuthScopeFlags authScopeOptionsFlags; + /// /// Used to store integrated platform management flags. /// - public List flags; + [ConfigField("Integrated Platform Management Flags", + ConfigFieldType.Enum, "Integrated Platform Management " + + "Flags for platform specific options.", + 2, "https://dev.epicgames.com/docs/api-ref/enums/eos-e-integrated-platform-management-flags")] + [JsonConverter(typeof(ListOfStringsToIntegratedPlatformManagementFlags))] + public IntegratedPlatformManagementFlags integratedPlatformManagementFlags; + + // This property exists to maintain backwards-compatibility with + // previous versions of the config json structures. + [JsonProperty] // Mark it so that it gets read + [JsonIgnore] // Ignore so that it does not get written + [Obsolete("This property is deprecated. Use the property integratedPlatformManagementFlags instead.")] + [JsonConverter(typeof(ListOfStringsToIntegratedPlatformManagementFlags))] + public IntegratedPlatformManagementFlags flags + { + get + { + return integratedPlatformManagementFlags; + } + set + { + integratedPlatformManagementFlags = value; + } + } + +#endif + + #endregion + + #region Thread Affinity & Various Time Budgets + + /// + /// Tick Budget; used to define the maximum amount of execution time the + /// EOS SDK can use each frame. + /// + [ConfigField("Tick Budget (ms)", + ConfigFieldType.Uint, + "Used to define the maximum amount of execution time the " + + "EOS SDK can use each frame.", + 3)] + public uint tickBudgetInMilliseconds; + + /// + /// TaskNetworkTimeoutSeconds; used to define the maximum number of + /// seconds the EOS SDK will allow network calls to run before failing + /// with EOS_TimedOut. This plugin treats any value that is less than or + /// equal to zero as using the default value for the EOS SDK, which is + /// 30 seconds. + /// + /// This value is only used when the is not + /// . + /// + /// + [ConfigField("Network Timeout Seconds", + ConfigFieldType.Double, + "Indicates the maximum number of seconds that (before " + + "first coming online) the EOS SDK will allow network calls to " + + "run before failing with EOS_TimedOut. This value does not apply " + + "after the EOS SDK has been initialized.", + 3)] + public double taskNetworkTimeoutSeconds; + + // This compile conditional is here so that when EOS is disabled, nothing is + // referenced in the Epic namespace. +#if !EOS_DISABLE + [ConfigField("Thread Affinity Options", + ConfigFieldType.WrappedInitializeThreadAffinity, + "Defines the thread affinity for threads started by the " + + "EOS SDK. Leave values at zero to use default platform settings.", + 3, "https://dev.epicgames.com/docs/api-ref/structs/eos-initialize-thread-affinity")] + public WrappedInitializeThreadAffinity threadAffinity; +#endif +#endregion + + #region Overlay Options + + /// + /// Always Send Input to Overlay </c>If true, the plugin will + /// always send input to the overlay from the C# side to native, and + /// handle showing the overlay. This doesn't always mean input makes it + /// to the EOS SDK. + /// + [ConfigField(OVERLAY_COMPATIBLE_PLATFORMS, + "Always Send Input to Overlay", + ConfigFieldType.Flag, + "If true, the plugin will always send input to the " + + "overlay from the C# side to native, and handle showing the " + + "overlay. This doesn't always mean input makes it to the EOS SDK.", + 4)] + public bool alwaysSendInputToOverlay; + + /// + /// Initial Button Delay. + /// + [ConfigField(OVERLAY_COMPATIBLE_PLATFORMS, + "Initial Button Delay", ConfigFieldType.Float, + "Initial Button Delay (if not set, whatever the default " + + "is will be used).", 4)] + [JsonConverter(typeof(StringToTypeConverter))] + public float initialButtonDelayForOverlay; + + /// + /// Repeat button delay for overlay. + /// + [ConfigField(OVERLAY_COMPATIBLE_PLATFORMS, + "Repeat Button Delay", ConfigFieldType.Float, + "Repeat button delay for the overlay. If not set, " + + "whatever the default is will be used.", 4)] + [JsonConverter(typeof(StringToTypeConverter))] + public float repeatButtonDelayForOverlay; + + // This compile conditional is here so that when EOS is disabled, in the + // Epic namespace is referenced. +#if !EOS_DISABLE + /// + /// When this combination of buttons is pressed on a controller, the + /// social overlay will toggle on. + /// Default to , and will + /// use that value if this configuration field is null, empty, or contains + /// only . + /// + [JsonConverter(typeof(ListOfStringsToInputStateButtonFlags))] + [ConfigField( + OVERLAY_COMPATIBLE_PLATFORMS, + "Default Activate Overlay Button", + ConfigFieldType.Enum, + "Users can press the button's associated with this value " + + "to activate the Epic Social Overlay. Not all combinations are " + + "valid; the SDK will log an error at the start of runtime if an " + + "invalid combination is selected.", 4)] + public InputStateButtonFlags toggleFriendsButtonCombination = InputStateButtonFlags.SpecialLeft; + +#endif + + #endregion /// /// Create a PlatformConfig by defining the platform it pertains to. @@ -63,26 +258,285 @@ public abstract class PlatformConfig : Config /// /// The platform to apply the config values on. /// - protected PlatformConfig(PlatformManager.Platform platform) : + protected PlatformConfig(PlatformManager.Platform platform) : base(PlatformManager.GetConfigFileName(platform)) { Platform = platform; } - /// - /// Returns a single IntegratedPlatformManagementFlags enum value that - /// results from a bitwise OR operation of all the - /// integratedPlatformManagementFlags flags on this config. - /// - /// An IntegratedPlatformManagementFlags enum value. + #region Logic for Migrating Override Values from Previous Structure + #if !EOS_DISABLE - public IntegratedPlatformManagementFlags GetIntegratedPlatformManagementFlags() + + protected sealed class NonOverrideableConfigValues : Config { - _ = IntegratedPlatformManagementFlagsExtensions.TryParse(flags, - out IntegratedPlatformManagementFlags flagsEnum); + public string deploymentID; + public string clientID; + public uint tickBudgetInMilliseconds; + public double taskNetworkTimeoutSeconds; + + [JsonConverter(typeof(ListOfStringsToPlatformFlags))] + public WrappedPlatformFlags platformOptionsFlags; + + [JsonConverter(typeof(ListOfStringsToAuthScopeFlags))] + public AuthScopeFlags authScopeOptionsFlags; + + [JsonConverter(typeof(ListOfStringsToIntegratedPlatformManagementFlags))] + public IntegratedPlatformManagementFlags integratedPlatformManagementFlags; + + public bool alwaysSendInputToOverlay; + + static NonOverrideableConfigValues() + { + RegisterFactory(() => new NonOverrideableConfigValues()); + } - return flagsEnum; + internal NonOverrideableConfigValues() : base("EpicOnlineServicesConfig.json") { } } + + internal sealed class OverrideableConfigValues : Config + { + [JsonConverter(typeof(ListOfStringsToPlatformFlags))] + public WrappedPlatformFlags platformOptionsFlags; + + [JsonConverter(typeof(StringToTypeConverter))] + public float? initialButtonDelayForOverlay; + + [JsonConverter(typeof(StringToTypeConverter))] + public float? repeatButtonDelayForOverlay; + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_networkWork; + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_storageIO; + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_webSocketIO; + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_P2PIO; + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_HTTPRequestIO; + + [JsonConverter(typeof(StringToTypeConverter))] + public ulong? ThreadAffinity_RTCIO; + + static OverrideableConfigValues() + { + RegisterFactory(() => new OverrideableConfigValues()); + } + + internal OverrideableConfigValues() : base("EpicOnlineServicesConfig.json") { } + } + + private static TK SelectValue(TK overrideValuesFromFieldMember, TK mainConfigValue) + { + // If the value in the overrides is not default, then it takes + // precedent + return !overrideValuesFromFieldMember.Equals(default) ? overrideValuesFromFieldMember : mainConfigValue; + } + + private void MigrateButtonDelays(EOSConfig overrideValuesFromFieldMember, OverrideableConfigValues mainOverrideableConfig) + { + // Import the values for initial button delay and repeat button + // delay + initialButtonDelayForOverlay = SelectValue( + overrideValuesFromFieldMember.initialButtonDelayForOverlay ?? 0, + mainOverrideableConfig.initialButtonDelayForOverlay ?? 0); + + repeatButtonDelayForOverlay = SelectValue( + overrideValuesFromFieldMember.repeatButtonDelayForOverlay ?? 0, + mainOverrideableConfig.repeatButtonDelayForOverlay ?? 0); + } + + private void MigrateThreadAffinity(EOSConfig overrideValuesFromFieldMember, OverrideableConfigValues mainOverrideableConfig) + { + threadAffinity ??= new(); + + // Import the values for thread initialization + threadAffinity.NetworkWork = SelectValue( + overrideValuesFromFieldMember.ThreadAffinity_networkWork, + mainOverrideableConfig.ThreadAffinity_networkWork) ?? 0; + + threadAffinity.StorageIo = SelectValue( + overrideValuesFromFieldMember.ThreadAffinity_storageIO, + mainOverrideableConfig.ThreadAffinity_storageIO) ?? 0; + + threadAffinity.WebSocketIo = SelectValue( + overrideValuesFromFieldMember.ThreadAffinity_webSocketIO, + mainOverrideableConfig.ThreadAffinity_webSocketIO) ?? 0; + + threadAffinity.P2PIo = SelectValue( + overrideValuesFromFieldMember.ThreadAffinity_P2PIO, + mainOverrideableConfig.ThreadAffinity_P2PIO) ?? 0; + + threadAffinity.HttpRequestIo = SelectValue( + overrideValuesFromFieldMember.ThreadAffinity_HTTPRequestIO, + mainOverrideableConfig.ThreadAffinity_HTTPRequestIO) ?? 0; + + threadAffinity.RTCIo = SelectValue( + overrideValuesFromFieldMember.ThreadAffinity_RTCIO, + mainOverrideableConfig.ThreadAffinity_RTCIO) ?? 0; + } + + private void MigrateOverrideableConfigValues(EOSConfig overrideValuesFromFieldMember, + OverrideableConfigValues mainOverrideableConfig) + { + // Import the values for platform option flags. + platformOptionsFlags |= overrideValuesFromFieldMember.platformOptionsFlags; + + MigrateButtonDelays(overrideValuesFromFieldMember, mainOverrideableConfig); + MigrateThreadAffinity(overrideValuesFromFieldMember, mainOverrideableConfig); + } + + protected virtual void MigrateNonOverrideableConfigValues(EOSConfig overrideValuesFromFieldMember, + NonOverrideableConfigValues mainNonOverrideableConfig) + { + authScopeOptionsFlags = mainNonOverrideableConfig.authScopeOptionsFlags; + + // Because by default EOSManager used to define the auth scope flags to default to + // the following, when migrating from the old configuration to the new configuration, + // set these auth scope options flags explicitly so that they are reflected in both + // the functionality and the user interface that displays the configuration + authScopeOptionsFlags |= AuthScopeFlags.BasicProfile; + authScopeOptionsFlags |= AuthScopeFlags.FriendsList; + authScopeOptionsFlags |= AuthScopeFlags.Presence; + + tickBudgetInMilliseconds = mainNonOverrideableConfig.tickBudgetInMilliseconds; + taskNetworkTimeoutSeconds = mainNonOverrideableConfig.taskNetworkTimeoutSeconds; + alwaysSendInputToOverlay = mainNonOverrideableConfig.alwaysSendInputToOverlay; + + MigratePlatformFlags(overrideValuesFromFieldMember, mainNonOverrideableConfig); + + integratedPlatformManagementFlags = IntegratedPlatformManagementFlags.Disabled; + integratedPlatformManagementFlags |= mainNonOverrideableConfig.integratedPlatformManagementFlags; + + ProductConfig productConfig = Get(); + string compDeploymentString = mainNonOverrideableConfig.deploymentID.ToLower(); + + foreach(Named dep in productConfig.Environments.Deployments) + { + if (!compDeploymentString.Equals(dep.Value.DeploymentId.ToString("N").ToLowerInvariant())) + { + continue; + } + + deployment = dep.Value; + break; + } + + foreach (Named creds in productConfig.Clients) + { + if (!creds.Value.ClientId.Equals(mainNonOverrideableConfig.clientID)) + { + continue; + } + + clientCredentials = creds.Value; + break; + } + } + + protected virtual void MigratePlatformFlags(EOSConfig overrideValuesFromFieldMember, + NonOverrideableConfigValues mainNonOverrideableConfig) + { + // The best (perhaps only) way to meaningfully migrate values from + // the overrideValues field member and the main config is to combine + // them, then filter them to exclude any platform flags that are + // incompatible with the platform for this Config. THIS is the + // primary reason it is necessary to warn the user and ask them to + // double check the values after migration. + WrappedPlatformFlags combinedPlatformFlags = mainNonOverrideableConfig.platformOptionsFlags; + + if (overrideValuesFromFieldMember != null) + { + combinedPlatformFlags |= overrideValuesFromFieldMember.platformOptionsFlags; + } + + WrappedPlatformFlags migratedPlatformFlags = WrappedPlatformFlags.None; + foreach (WrappedPlatformFlags flag in EnumUtility.GetEnumerator(combinedPlatformFlags)) + { + switch (flag) + { + case WrappedPlatformFlags.None: + case WrappedPlatformFlags.LoadingInEditor: + case WrappedPlatformFlags.DisableOverlay: + case WrappedPlatformFlags.DisableSocialOverlay: + case WrappedPlatformFlags.Reserved1: + migratedPlatformFlags |= flag; + break; + case WrappedPlatformFlags.WindowsEnableOverlayD3D9: + case WrappedPlatformFlags.WindowsEnableOverlayD3D10: + case WrappedPlatformFlags.WindowsEnableOverlayOpengl: + if (Platform == PlatformManager.Platform.Windows) + { + migratedPlatformFlags |= flag; + } + break; + case WrappedPlatformFlags.ConsoleEnableOverlayAutomaticUnloading: + if (Platform == PlatformManager.Platform.Console) + { + migratedPlatformFlags |= flag; + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + platformOptionsFlags = migratedPlatformFlags; + } + + + protected override void MigrateConfig() + { + // The following code takes any values that used to exist within the + // overrideValues field member, and places them into the + // appropriate new field members. It also takes values from the + // main EOSConfig - if the values are not defined in the + // overrideValues. + + // Do nothing if the values have already been moved, or if + // overrideValues is null. +#pragma warning disable CS0612 // Type or member is obsolete + if (null != overrideValues) + { + // This config represents the set of values that previously were + // overrideable from the editor window. These values should take + // priority over the main config if they are not default values. + OverrideableConfigValues mainOverrideableConfigValues = Get(); +#pragma warning disable CS0612 // Type or member is obsolete + MigrateOverrideableConfigValues(overrideValues, mainOverrideableConfigValues); +#pragma warning restore CS0612 // Type or member is obsolete + } +#pragma warning restore CS0612 // Type or member is obsolete + + // This config represents the set of values that were not + // overrideable from the editor window. The migrated values should + // favor these set of values. + NonOverrideableConfigValues mainNonOverrideableConfigValuesThatCouldNotBeOverridden = + Get(); +#pragma warning disable CS0612 // Type or member is obsolete + MigrateNonOverrideableConfigValues(overrideValues, + mainNonOverrideableConfigValuesThatCouldNotBeOverridden); +#pragma warning restore CS0612 // Type or member is obsolete + + // Notify the user of the migration, encourage them to double check + // that migration was successful. + Debug.LogWarning( + $"Configuration values for {GetType().Name} have been " + + "migrated. Please double check your configuration in EOS " + + "Plugin -> EOS Configuration to make sure that the " + + "migration was successful."); + } + + #endif + +#pragma warning restore CS0618 // Type or member is obsolete + + #endregion } } \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/ProductConfig.cs b/com.playeveryware.eos/Runtime/Core/Config/ProductConfig.cs new file mode 100644 index 000000000..7ac8bdadc --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/ProductConfig.cs @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using Common; + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + +#if !EXTERNAL_TO_UNITY + using UnityEngine; +#endif + + /// + /// Contains information about the product entered by the user from the Epic + /// Developer Portal. + /// + [ConfigGroup("Product Configuration", new[] { "", "Deployment Configuration" }, false)] + public class ProductConfig : Config + { + /// + /// The product ID is a unique GUID labeled "Product ID" in the Epic + /// Developer Portal. The name for this value can be set to anything - + /// it is used as a label for user interface purposes - and is allowed + /// to differ from the label given to it on the Developer Portal. + /// + [ConfigField("Product Name", + ConfigFieldType.Text, + "Enter your product name as it appears in the EOS Dev " + + "Portal here.", + 0)] + public string ProductName; + + [ConfigField("Product Id", + ConfigFieldType.Guid, + "Enter your Product Id as it appears in the EOS Dev " + + "Portal here.", + 0)] + public Guid ProductId; + + [ConfigField("Version", + ConfigFieldType.Text, + "Use this to indicate to the EOS SDK your game version.", + 0)] + public string ProductVersion; + + /// + /// This additional flag determines whether or not the productconfig has + /// been imported. The reason that schema alone is not sufficient for + /// this is because product_config is a new file altogether, so when it + /// is first created, it will have the newest schema version, and + /// migration will need to take place. + /// + [JsonProperty("imported")] + private bool _configImported = false; + +#if !EOS_DISABLE + /// + /// The set of Clients as defined within the Epic Developer Portal. For + /// EOS to function, at least one of these must be set, and the + /// platform config needs to indicate which one to use. (If none is + /// explicitly indicated, and only one is defined, that one will be + /// used). + /// + [ConfigField("Client Credentials", + ConfigFieldType.SetOfClientCredentials, + "Enter the client credentials you have defined in the " + + "Epic Dev Portal.", 1)] + public SetOfNamed Clients = new("Client"); +#endif + + /// + /// The set of Sandboxes as defined within the Epic Developer Portal. + /// For EOS to function, at least one of these must be set, and it must + /// match the deployment indicated by the platform config. + /// + [ConfigField("Production Environments", + ConfigFieldType.ProductionEnvironments, + "Enter the details of your deployment and sandboxes as they " + + "exist within the Epic Dev Portal.", 1)] + public ProductionEnvironments Environments = new(); + + static ProductConfig() + { + RegisterFactory(() => new ProductConfig()); + } + + protected override bool NeedsMigration() + { + return base.NeedsMigration() || !_configImported; + } + + protected ProductConfig() : base("eos_product_config.json") { } + + #region Functionality to migrate from old configuration to new + + internal class PreviousEOSConfig : Config + { + public string productName; + public string productVersion; + public string productID; + public List sandboxDeploymentOverrides; + public string sandboxID; + public string deploymentID; + public string clientSecret; + public string clientID; + public string encryptionKey; + + static PreviousEOSConfig() + { + RegisterFactory(() => new PreviousEOSConfig()); + } + + protected PreviousEOSConfig() : base("EpicOnlineServicesConfig.json") { } + } + + private void MigrateProductNameVersionAndId(PreviousEOSConfig config) + { + ProductName = config.productName; + ProductVersion = config.productVersion; + + if (!Guid.TryParse(config.productID, out ProductId)) + { + Debug.LogWarning("Could not parse product ID."); + } + } + + private void MigrateClientCredentials(PreviousEOSConfig config) + { +#if !EOS_DISABLE + // Import the old config client stuff + Clients.Add(new EOSClientCredentials(config.clientID, config.clientSecret, + config.encryptionKey)); +#endif + } + + private void MigrateSandboxAndDeployment(PreviousEOSConfig config) + { + // Import explicitly set sandbox and deployment + SandboxId sandboxId = new() + { + Value = config.sandboxID + }; + + Deployment oldDeployment = new() + { + DeploymentId = Guid.Parse(config.deploymentID), + SandboxId = sandboxId + }; + + if (!Environments.AddDeployment(oldDeployment)) + { + Debug.LogWarning("Could not import deployment " + + "details from old config file. Please " + + "reach out for support if you need " + + "assistance."); + } + } + + private void MigrateSandboxAndDeploymentOverrides(PreviousEOSConfig config) + { + // Import each of the overrides + foreach (var overrideValues in config.sandboxDeploymentOverrides) + { + SandboxId overrideSandbox = new() { Value = overrideValues.sandboxID }; + Deployment overrideDeployment = new() + { + DeploymentId = Guid.Parse(overrideValues.deploymentID), + SandboxId = overrideSandbox + }; + + Environments.Deployments.Add(overrideDeployment); + } + } + + protected override void MigrateConfig() + { + Environments ??= new(); + + PreviousEOSConfig oldConfig = Get(); + + MigrateProductNameVersionAndId(oldConfig); + MigrateClientCredentials(oldConfig); + MigrateSandboxAndDeployment(oldConfig); + MigrateSandboxAndDeploymentOverrides(oldConfig); + + _configImported = true; + } + #endregion + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/ProductConfig.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/ProductConfig.cs.meta new file mode 100644 index 000000000..832ec7a7a --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/ProductConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff93e4c2de6a9d5429102c69b84b5b5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/ProductionEnvironments.cs b/com.playeveryware.eos/Runtime/Core/Config/ProductionEnvironments.cs new file mode 100644 index 000000000..bd8735878 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/ProductionEnvironments.cs @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using Common; + + /// + /// This class contains information about the set of deployments and + /// sandboxes that a single EOS plugin project can be configured to + /// deploy to. + /// + public class ProductionEnvironments + { + /// + /// Deployments are different environments within defined Sandboxes. + /// One sandbox can have more than one Deployment. + /// + public SetOfNamed Deployments { get; } = new("Deployment"); + + /// + /// Sandboxes are different siloed categories of production environment. + /// One sandbox can have more than one deployment. + /// + public SetOfNamed Sandboxes { get; } = new("Sandbox"); + + public ProductionEnvironments() + { + // Set the predicate for removing a sandbox so a sandbox that is + // associated with a deployment does not get removed. + Sandboxes.SetRemovePredicate(CanSandboxBeRemoved); + } + + /// + /// Removes a Sandbox from the Production Environment. + /// + /// + /// The Sandbox to remove from the production environment. + /// + /// + /// True if the Sandbox was removed, false otherwise. If there is a + /// defined deployment that references the Sandbox, then removing it is + /// disallowed. + /// + private bool CanSandboxBeRemoved(SandboxId sandbox) + { + foreach (Named deployment in Deployments) + { + if (deployment.Value.SandboxId.Equals(sandbox)) + { + return false; + } + } + + return true; + } + + /// + /// Adds a Deployment to the Production Environment, adding the sandbox + /// if it does not already exist in the set of sandboxes. + /// + /// + /// The Deployment to add. + /// + /// + /// True if the deployment was added, false otherwise. + /// + public bool AddDeployment(Deployment deployment) + { + // Add the sandbox (will do nothing if the sandbox already exists). + Sandboxes.Add(deployment.SandboxId); + + // Add the deployment to the list of deployments + return Deployments.Add(deployment); + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/ProductionEnvironments.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/ProductionEnvironments.cs.meta new file mode 100644 index 000000000..eb3e28f99 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/ProductionEnvironments.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3797ab819c740744bbd8091110230a5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Config/SandboxId.cs b/com.playeveryware.eos/Runtime/Core/Config/SandboxId.cs new file mode 100644 index 000000000..410765f72 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/SandboxId.cs @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System.Text.RegularExpressions; + using System; + + public struct SandboxId : IEquatable + { + private string _value; + private const string PreProductionEnvironmentRegex = @"^p\-[a-zA-Z\d]{30}$"; + + public string Value + { + readonly get + { + return _value; + } + set + { + // allow null value + if (value == null) + { + _value = null; + return; + } + + if (!Guid.TryParse(value, out _)) + { + if (!Regex.IsMatch(value, PreProductionEnvironmentRegex)) + { + throw new ArgumentException( + "Value for SandboxId must either be " + + "parseable to a Guid, or it must start with a " + + "lowercase 'p', followed by a dash and thirty " + + "letter characters."); + } + } + else + { + // If the Guid was correctly parsed, then considering that + // the EOS SDK prefers SandboxId to be lowercase without + // dashes, do this just to be sure. + value = value.Replace("-", ""); + } + + // Whether the value was correctly matched to the regex or it + // was a Guid, it still should be lowercase. + _value = value.ToLower(); + } + } + + public readonly bool Equals(SandboxId other) + { + return _value == other._value; + } + + public override readonly bool Equals(object obj) + { + return obj is SandboxId sandboxId && Equals(sandboxId); + } + + public override readonly int GetHashCode() + { + return (_value?.GetHashCode()) ?? 0; + } + + public override readonly string ToString() + { + return _value; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Config/SandboxId.cs.meta b/com.playeveryware.eos/Runtime/Core/Config/SandboxId.cs.meta new file mode 100644 index 000000000..68ee7915e --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Config/SandboxId.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 947b5bafa7a8e4a419fe5e2b31b98056 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/EOSManager.cs b/com.playeveryware.eos/Runtime/Core/EOSManager.cs index 9088656d7..5e3307bd6 100644 --- a/com.playeveryware.eos/Runtime/Core/EOSManager.cs +++ b/com.playeveryware.eos/Runtime/Core/EOSManager.cs @@ -20,6 +20,12 @@ * SOFTWARE. */ +// Uncomment the following line to enable logging of application focus state +// changes. If this is on it can clutter the log window and make debugging +// difficult, so please enable it when you need to diagnose application focus +// state-related issues. +//#define LOG_APPLICATION_FOCUS_CHANGE + // Don't shut down the interface if running in the editor. // According to the Epic documentation, shutting down this will disable a given loaded // instance of the SDK from ever initializing again. Which is bad because Unity often (always?) loads a library just once @@ -51,7 +57,9 @@ namespace PlayEveryWare.EpicOnlineServices { - using Extensions; + //using Extensions; + using Common; + using Common.Extensions; using UnityEngine; using System; using System.Collections.Generic; @@ -64,11 +72,10 @@ namespace PlayEveryWare.EpicOnlineServices using Epic.OnlineServices.Logging; using Epic.OnlineServices.Connect; using Epic.OnlineServices.UI; -#endif -#if !EOS_DISABLE using Epic.OnlineServices.Presence; + using Extensions; using System.Diagnostics; using System.Globalization; using UnityEngine.Assertions; @@ -82,7 +89,6 @@ namespace PlayEveryWare.EpicOnlineServices using LoginStatusChangedCallbackInfo = Epic.OnlineServices.Auth.LoginStatusChangedCallbackInfo; using Utility; - using JsonUtility = PlayEveryWare.EpicOnlineServices.Utility.JsonUtility; using LogoutCallbackInfo = Epic.OnlineServices.Auth.LogoutCallbackInfo; using LogoutOptions = Epic.OnlineServices.Auth.LogoutOptions; using OnLogoutCallback = Epic.OnlineServices.Auth.OnLogoutCallback; @@ -271,7 +277,7 @@ public ProductUserId GetProductUserId() /// public string GetProductId() { - return Config.Get().productID; + return Config.Get().ProductId.ToString("N").ToLowerInvariant(); } //------------------------------------------------------------------------- @@ -281,7 +287,7 @@ public string GetProductId() /// public string GetSandboxId() { - return Config.Get().sandboxID; + return PlatformManager.GetPlatformConfig().deployment.SandboxId.ToString(); } //------------------------------------------------------------------------- @@ -291,7 +297,7 @@ public string GetSandboxId() /// public string GetDeploymentID() { - return Config.Get().deploymentID; + return PlatformManager.GetPlatformConfig().deployment.DeploymentId.ToString("N").ToLowerInvariant(); } //------------------------------------------------------------------------- @@ -301,7 +307,7 @@ public string GetDeploymentID() /// public bool IsEncryptionKeyValid() { - return Config.Get().IsEncryptionKeyValid(); + return PlatformManager.GetPlatformConfig().clientCredentials.IsEncryptionKeyValid(); } //------------------------------------------------------------------------- @@ -324,7 +330,7 @@ public bool HasLoggedInWithConnect() public bool ShouldOverlayReceiveInput() { return (s_isOverlayVisible && s_DoesOverlayHaveExcusiveInput) - || Config.Get().alwaysSendInputToOverlay + || PlatformManager.GetPlatformConfig().alwaysSendInputToOverlay ; } @@ -437,30 +443,7 @@ public void RemoveManager() where T : IEOSSubManager //------------------------------------------------------------------------- private Result InitializePlatformInterface() { - EOSConfig configData = Config.Get(); - IPlatformSpecifics platformSpecifics = EOSManagerPlatformSpecificsSingleton.Instance; - - print("InitializePlatformInterface: platformSpecifics.GetType() = " + platformSpecifics.GetType()); - - EOSInitializeOptions initOptions = new EOSInitializeOptions(); - - print("InitializePlatformInterface: initOptions.GetType() = " + initOptions.GetType()); - - initOptions.options.ProductName = configData.productName; - initOptions.options.ProductVersion = configData.productVersion; - initOptions.options.OverrideThreadAffinity = new InitializeThreadAffinity(); - - initOptions.options.AllocateMemoryFunction = IntPtr.Zero; - initOptions.options.ReallocateMemoryFunction = IntPtr.Zero; - initOptions.options.ReleaseMemoryFunction = IntPtr.Zero; - - var overrideThreadAffinity = new InitializeThreadAffinity(); - - configData.ConfigureOverrideThreadAffinity(ref overrideThreadAffinity); - - initOptions.options.OverrideThreadAffinity = overrideThreadAffinity; - - platformSpecifics.ConfigureSystemInitOptions(ref initOptions); + EOSInitializeOptions initOptions = ConfigurationUtility.GetEOSInitializeOptions(); #if UNITY_PS4 && !UNITY_EDITOR // On PS4, RegisterForPlatformNotifications is called at a later time by EOSPSNManager @@ -468,74 +451,18 @@ private Result InitializePlatformInterface() RegisterForPlatformNotifications(); #endif - return PlatformInterface.Initialize(ref (initOptions as EOSInitializeOptions).options); + return PlatformInterface.Initialize(ref initOptions.options); } //------------------------------------------------------------------------- private PlatformInterface CreatePlatformInterface() { - EOSConfig configData = Config.Get(); - IPlatformSpecifics platformSpecifics = EOSManagerPlatformSpecificsSingleton.Instance; - - EOSCreateOptions platformOptions = new EOSCreateOptions(); - - - platformOptions.options.CacheDirectory = platformSpecifics.GetTempDir(); - platformOptions.options.IsServer = configData.isServer; - platformOptions.options.Flags = -#if UNITY_EDITOR - PlatformFlags.LoadingInEditor; -#else - configData.GetPlatformFlags(); -#endif - if (configData.IsEncryptionKeyValid()) - { - platformOptions.options.EncryptionKey = configData.encryptionKey; - } - else - { - print( - "EOS config data does not contain a valid encryption key which is needed for Player Data Storage and Title Storage.", - LogType.Warning); - } - - platformOptions.options.OverrideCountryCode = null; - platformOptions.options.OverrideLocaleCode = null; - platformOptions.options.ProductId = configData.productID; - platformOptions.options.SandboxId = configData.sandboxID; - platformOptions.options.DeploymentId = configData.deploymentID; - - platformOptions.options.TickBudgetInMilliseconds = configData.tickBudgetInMilliseconds; - - // configData has to serialize to JSON, so it doesn't represent null - // If the value is <= 0, then set it to null, which the EOS SDK will handle by using default of 30 seconds. - platformOptions.options.TaskNetworkTimeoutSeconds = configData.taskNetworkTimeoutSeconds > 0 ? configData.taskNetworkTimeoutSeconds : null; - - var clientCredentials = new ClientCredentials - { - ClientId = configData.clientID, - ClientSecret = configData.clientSecret - }; - platformOptions.options.ClientCredentials = clientCredentials; + EOSCreateOptions platformOptions = ConfigurationUtility.GetEOSCreateOptions(); + PlatformInterface platformInterface = PlatformInterface.Create(ref platformOptions.options); #if !(UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) - var createIntegratedPlatformOptionsContainerOptions = new Epic.OnlineServices.IntegratedPlatform.CreateIntegratedPlatformOptionsContainerOptions(); - var integratedPlatformOptionsContainer = new Epic.OnlineServices.IntegratedPlatform.IntegratedPlatformOptionsContainer(); - var integratedPlatformOptionsContainerResult = Epic.OnlineServices.IntegratedPlatform.IntegratedPlatformInterface.CreateIntegratedPlatformOptionsContainer(ref createIntegratedPlatformOptionsContainerOptions, out integratedPlatformOptionsContainer); - - if (integratedPlatformOptionsContainerResult != Result.Success) - { - print($"Error creating integrated platform container: {integratedPlatformOptionsContainerResult}"); - } - platformOptions.options.IntegratedPlatformOptionsContainerHandle = integratedPlatformOptionsContainer; -#endif - platformSpecifics.ConfigureSystemPlatformCreateOptions(ref platformOptions); - - PlatformInterface platformInterface = PlatformInterface.Create(ref (platformOptions as EOSCreateOptions).options); - -#if !(UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) - integratedPlatformOptionsContainer.Release(); + platformOptions.options.IntegratedPlatformOptionsContainerHandle.Release(); #endif return platformInterface; @@ -544,20 +471,17 @@ private PlatformInterface CreatePlatformInterface() //------------------------------------------------------------------------- private void InitializeOverlay(IEOSCoroutineOwner coroutineOwner) { - EOSConfig configData = Config.Get(); - // Sets the button for the bringing up the overlay var friendToggle = new SetToggleFriendsButtonOptions { - ButtonCombination = configData.GetToggleFriendsButtonCombinationFlags() + ButtonCombination = PlatformManager.GetPlatformConfig().toggleFriendsButtonCombination }; UIInterface uiInterface = Instance.GetEOSPlatformInterface().GetUIInterface(); uiInterface.SetToggleFriendsButton(ref friendToggle); EOSManagerPlatformSpecificsSingleton.Instance.InitializeOverlay(coroutineOwner); - AddNotifyDisplaySettingsUpdatedOptions addNotificationData = - new AddNotifyDisplaySettingsUpdatedOptions(); + AddNotifyDisplaySettingsUpdatedOptions addNotificationData = new(); GetEOSUIInterface().AddNotifyDisplaySettingsUpdated(ref addNotificationData, null, (ref OnDisplaySettingsUpdatedCallbackInfo data) => @@ -575,6 +499,43 @@ public void Init(IEOSCoroutineOwner coroutineOwner) Init(coroutineOwner, EOSPackageInfo.ConfigFileName); } + private void ConfigureCommandLineOptions() + { + // TODO: Make more complete the support for command line arguments. + var epicArgs = GetCommandLineArgsFromEpicLauncher(); + + if (string.IsNullOrWhiteSpace(epicArgs.epicSandboxID)) + { + return; + } + + var definedProductionEnvironments = Config.Get().Environments; + bool sandboxOverridden = false; + foreach (Named sandbox in definedProductionEnvironments.Sandboxes) + { + if (sandbox.Value.ToString() != epicArgs.epicSandboxID.ToLower()) + { + continue; + } + + PlatformManager.GetPlatformConfig().deployment.SandboxId = sandbox.Value; + sandboxOverridden = true; + Debug.Log($"SandboxID was overridden to be \"{sandbox.Value}\" by a command line parameter."); + break; + } + + if (!sandboxOverridden) + { + Debug.LogWarning( + $"The SandboxID " + + $"\"{epicArgs.epicSandboxID}\" specified by " + + $"command line argument is not a valid id. " + + $"Defaulting to SandboxID " + + $"\"{PlatformManager.GetPlatformConfig().deployment.SandboxId}\" " + + $"defined in the configuration."); + } + } + private void Init(IEOSCoroutineOwner coroutineOwner, string configFileName) { if (GetEOSPlatformInterface() != null) @@ -607,12 +568,7 @@ private void Init(IEOSCoroutineOwner coroutineOwner, string configFileName) InitializeLogLevels(); #endif - var epicArgs = GetCommandLineArgsFromEpicLauncher(); - - if (!string.IsNullOrWhiteSpace(epicArgs.epicSandboxID)) - { - Config.Get().SetDeployment(epicArgs.epicSandboxID); - } + ConfigureCommandLineOptions(); Result initResult = InitializePlatformInterface(); @@ -849,19 +805,10 @@ static private LoginOptions MakeLoginOptions(LoginCredentialType loginType, Token = token }; - AuthScopeFlags scopeFlags = (AuthScopeFlags.BasicProfile | - AuthScopeFlags.FriendsList | - AuthScopeFlags.Presence); - - if (Config.Get().GetAuthScopeFlags() != AuthScopeFlags.NoFlags) - { - scopeFlags = Config.Get().GetAuthScopeFlags(); - } - return new LoginOptions { Credentials = loginCredentials, - ScopeFlags = scopeFlags + ScopeFlags = PlatformManager.GetPlatformConfig().authScopeOptionsFlags, }; } @@ -905,11 +852,11 @@ public struct EpicLauncherArgs /// See https://dev.epicgames.com/docs/services/en-US/Interfaces/Auth/index.html#epicgameslauncher /// /// EpicLauncherArgs struct - public EpicLauncherArgs GetCommandLineArgsFromEpicLauncher() + public static EpicLauncherArgs GetCommandLineArgsFromEpicLauncher() { var epicLauncherArgs = new EpicLauncherArgs(); - void ConfigureEpicArgument(string argument, ref string argumentString) + static void ConfigureEpicArgument(string argument, ref string argumentString) { int startIndex = argument.IndexOf('=') + 1; if (!(startIndex < 0 || startIndex > argument.Length)) @@ -1755,9 +1702,11 @@ public void OnApplicationPause(bool isPaused) //------------------------------------------------------------------------- public void OnApplicationFocus(bool hasFocus) { +#if LOG_APPLICATION_FOCUS_CHANGE bool hadFocus = s_hasFocus; - s_hasFocus = hasFocus; print($"EOSSingleton.OnApplicationFocus: HasFocus {hadFocus} -> {s_hasFocus}"); +#endif + s_hasFocus = hasFocus; // // Poll for the latest application constrained state as we're about // // to need it to determine the appropriate EOS application status diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/EOSClientCredentials.cs b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/EOSClientCredentials.cs new file mode 100644 index 000000000..52fab22ce --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/EOSClientCredentials.cs @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#if !EOS_DISABLE + +namespace PlayEveryWare.EpicOnlineServices +{ + using Newtonsoft.Json; + using PlayEveryWare.EpicOnlineServices.Utility; + using System; + using System.Security.Cryptography; + using System.Text.RegularExpressions; + + public class EOSClientCredentials : IEquatable + { + public string ClientId; + public string ClientSecret; + public string EncryptionKey { get; private set; } + + private static readonly Regex s_invalidEncryptionKeyRegex; + + static EOSClientCredentials() + { + s_invalidEncryptionKeyRegex = new Regex("[^0-9a-fA-F]"); + } + + public EOSClientCredentials() + { + // Randomly generate a 32 byte hex key + byte[] randomBytes = new byte[32]; + using RandomNumberGenerator rng = RandomNumberGenerator.Create(); + rng.GetBytes(randomBytes); + + EncryptionKey = BitConverter.ToString(randomBytes).Replace( + "-", string.Empty); + } + + [JsonConstructor] + public EOSClientCredentials( + string clientId, + string clientSecret, + string encryptionKey) + { + ClientId = clientId; + ClientSecret = clientSecret; + EncryptionKey = encryptionKey; + } + + /// + /// Determines whether an encryption key string is valid. + /// + /// + /// True if the encryption key is valid, false otherwise. + /// + public static bool IsEncryptionKeyValid(string encryptionKey) + { + return + //key not null + encryptionKey != null && + //key is 64 characters + encryptionKey.Length == 64 && + //key is all hex characters + !s_invalidEncryptionKeyRegex.Match(encryptionKey).Success; + } + + /// + /// Determines whether the encryption key for the credentials is valid. + /// + /// + /// True if the encryption key is valid, false otherwise. + /// + public bool IsEncryptionKeyValid() + { + return IsEncryptionKeyValid(EncryptionKey); + } + + public bool Equals(EOSClientCredentials other) + { + if (other == null) + { + return false; + } + + return ClientId == other.ClientId && + ClientSecret == other.ClientSecret; + } + + public override bool Equals(object other) + { + return other is EOSClientCredentials otherCreds && Equals(otherCreds); + } + + public override int GetHashCode() + { + return HashUtility.Combine(ClientId, ClientSecret); + } + } +} + +#endif \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/EOSClientCredentials.cs.meta b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/EOSClientCredentials.cs.meta new file mode 100644 index 000000000..8e17ee184 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/EOSClientCredentials.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6d7d8d03ed2bea4b81b34a7d55f8a85 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/IntegratedPlatformManagementFlagsExtensions.cs b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/IntegratedPlatformManagementFlagsExtensions.cs index 907f804fd..fac7aae12 100644 --- a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/IntegratedPlatformManagementFlagsExtensions.cs +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/IntegratedPlatformManagementFlagsExtensions.cs @@ -41,6 +41,7 @@ public static class IntegratedPlatformManagementFlagsExtensions /// public static Dictionary CustomMappings { get; } = new() { + {"Nothing", IntegratedPlatformManagementFlags.Disabled }, {"EOS_IPMF_Disabled", IntegratedPlatformManagementFlags.Disabled }, {"EOS_IPMF_LibraryManagedByApplication", IntegratedPlatformManagementFlags.LibraryManagedByApplication }, diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/PlatformFlagsExtensions.cs b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/PlatformFlagsExtensions.cs index 7baa957b3..bb806e544 100644 --- a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/PlatformFlagsExtensions.cs +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/PlatformFlagsExtensions.cs @@ -90,6 +90,16 @@ public static bool TryParse(IList stringFlags, out PlatformFlags result) { return EnumUtility.TryParse(stringFlags, CustomMappings, out result); } + + public static WrappedPlatformFlags Wrap(this PlatformFlags internalFlags) + { + return (WrappedPlatformFlags)(int)internalFlags; + } + + public static PlatformFlags Unwrap(this WrappedPlatformFlags wrappedFlags) + { + return (PlatformFlags)(ulong)wrappedFlags; + } } } #endif \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedInitializeThreadAffinity.cs b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedInitializeThreadAffinity.cs new file mode 100644 index 000000000..914332f1f --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedInitializeThreadAffinity.cs @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if !EOS_DISABLE + +namespace PlayEveryWare.EpicOnlineServices +{ + using Epic.OnlineServices.Platform; + using Common; + + public class WrappedInitializeThreadAffinity : Wrapped + { + /// + /// Any thread related to network management that is not IO. + /// + [ConfigField("Network", ConfigFieldType.Ulong, "Any thread related to network management that is not IO.")] + public ulong NetworkWork + { + get + { + return _value.NetworkWork; + } + set + { + _value.NetworkWork = value; + } + } + + /// + /// Any thread that will interact with a storage device. + /// + [ConfigField("Storage IO", ConfigFieldType.Ulong, "Any thread that will interact with a storage device.")] + public ulong StorageIo + { + get + { + return _value.StorageIo; + } + set + { + _value.StorageIo = value; + } + } + + /// + /// Any thread that will generate web socket IO. + /// + [ConfigField("Web Socket IO", ConfigFieldType.Ulong, "Any thread that will generate web socket IO.")] + public ulong WebSocketIo + { + get + { + return _value.WebSocketIo; + } + set + { + _value.WebSocketIo = value; + } + } + + /// + /// Any thread that will generate IO related to P2P traffic and management. + /// + [ConfigField("P2P IO", ConfigFieldType.Ulong, "Any thread that will generate IO related to P2P traffic and management.")] + public ulong P2PIo + { + get + { + return _value.P2PIo; + } + set + { + _value.P2PIo = value; + } + } + + /// + /// Any thread that will generate http request IO. + /// + [ConfigField("HTTP Request IO", ConfigFieldType.Ulong, "Any thread that will generate http request IO.")] + public ulong HttpRequestIo + { + get + { + return _value.HttpRequestIo; + } + set + { + _value.HttpRequestIo = value; + } + } + + /// + /// Any thread that will generate IO related to RTC traffic and management. + /// + [ConfigField("RTC IO", ConfigFieldType.Ulong, "Any thread that will generate IO related to RTC traffic and management.")] + public ulong RTCIo + { + get + { + return _value.RTCIo; + } + set + { + _value.RTCIo = value; + } + } + + /// + /// Main thread of the external overlay + /// + [ConfigField("Embedded Overlay Main Thread", ConfigFieldType.Ulong, "Main thread of the external overlay.")] + public ulong EmbeddedOverlayMainThread + { + get + { + return _value.EmbeddedOverlayMainThread; + } + set + { + _value.EmbeddedOverlayMainThread = value; + } + } + + /// + /// Worker threads of the external overlay + /// + [ConfigField("Embedded Overlay Worker Threads", ConfigFieldType.Ulong, "Worker threads of the external overlay.")] + public ulong EmbeddedOverlayWorkerThreads + { + get + { + return _value.EmbeddedOverlayWorkerThreads; + } + set + { + _value.EmbeddedOverlayWorkerThreads = value; + } + } + } +} + +#endif \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedInitializeThreadAffinity.cs.meta b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedInitializeThreadAffinity.cs.meta new file mode 100644 index 000000000..383041e56 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedInitializeThreadAffinity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9c19f972245b534d978db954e1bb988 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedPlatformFlags.cs b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedPlatformFlags.cs new file mode 100644 index 000000000..5c7c193bc --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedPlatformFlags.cs @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + + /// + /// This enum is a 1:1 "duplicate" of the PlatformFlags enum provided by the + /// EOS SDK. The reason it is implemented here is due to the restricted + /// nature of Unity's support for the underlying data type for enum classes + /// in C#. Unity has some interesting edge cases in this regard, one of + /// which is that various editor utilities cannot be utilized for enums + /// whose underlying type is of an unsigned number type. + /// + [System.Flags] + public enum WrappedPlatformFlags : int + { + None = 0x0, + + /// + /// A bit that indicates the SDK is being loaded in a game editor, like Unity or UE4 Play-in-Editor + /// + LoadingInEditor = 0x00001, + + /// + /// A bit that indicates the SDK should skip initialization of the overlay, which is used by the in-app purchase flow and social overlay. This bit is implied by + /// + DisableOverlay = 0x00002, + + /// + /// A bit that indicates the SDK should skip initialization of the social overlay, which provides an overlay UI for social features. This bit is implied by or + /// + DisableSocialOverlay = 0x00004, + + /// + /// A reserved bit + /// + Reserved1 = 0x00008, + + /// + /// A bit that indicates your game would like to opt-in to experimental Direct3D 9 support for the overlay. This flag is only relevant on Windows + /// + WindowsEnableOverlayD3D9 = 0x00010, + + /// + /// A bit that indicates your game would like to opt-in to experimental Direct3D 10 support for the overlay. This flag is only relevant on Windows + /// + WindowsEnableOverlayD3D10 = 0x00020, + + /// + /// A bit that indicates your game would like to opt-in to experimental OpenGL support for the overlay. This flag is only relevant on Windows + /// + WindowsEnableOverlayOpengl = 0x00040, + + /// + /// A bit that indicates your game would like to opt-in to automatic unloading of the overlay module when possible. This flag is only relevant on Consoles + /// + ConsoleEnableOverlayAutomaticUnloading = 0x00080 + } + + public static class WrappedPlatformFlagsExtensions + { + public static bool IsSupported(this WrappedPlatformFlags platformFlags, PlatformManager.Platform platform) + { + switch (platformFlags) + { + case WrappedPlatformFlags.None: + case WrappedPlatformFlags.LoadingInEditor: + case WrappedPlatformFlags.DisableOverlay: + case WrappedPlatformFlags.DisableSocialOverlay: + case WrappedPlatformFlags.Reserved1: + return true; + case WrappedPlatformFlags.WindowsEnableOverlayD3D9: + case WrappedPlatformFlags.WindowsEnableOverlayD3D10: + case WrappedPlatformFlags.WindowsEnableOverlayOpengl: + if (platform == PlatformManager.Platform.Windows) + { + return true; + } + break; + case WrappedPlatformFlags.ConsoleEnableOverlayAutomaticUnloading: + if (platform == PlatformManager.Platform.Console) + { + return true; + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(platformFlags), platformFlags, null); + } + + return false; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedPlatformFlags.cs.meta b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedPlatformFlags.cs.meta new file mode 100644 index 000000000..884e062e1 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/WrappedPlatformFlags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7179e83b4de4b6741bf0f025c7eaca91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/IPlatformSpecifics.cs b/com.playeveryware.eos/Runtime/Core/IPlatformSpecifics.cs index d46588546..65190cd10 100644 --- a/com.playeveryware.eos/Runtime/Core/IPlatformSpecifics.cs +++ b/com.playeveryware.eos/Runtime/Core/IPlatformSpecifics.cs @@ -35,16 +35,20 @@ public interface IPlatformSpecifics string GetDynamicLibraryExtension(); -//#if EOS_DYNAMIC_BINDINGS + //#if EOS_DYNAMIC_BINDINGS // Only called if EOS_DYNAMIC_BINDINGS is defined void LoadDelegatesWithEOSBindingAPI(); -//#endif + //#endif + // The EXTERNAL_TO_UNITY block is here to enable the compilation of this + // code file outside of the context of Unity altogether. +#if !EXTERNAL_TO_UNITY void ConfigureSystemInitOptions(ref EOSInitializeOptions initializeOptions); - void ConfigureSystemPlatformCreateOptions(ref EOSCreateOptions createOptions); - void InitializeOverlay(IEOSCoroutineOwner owner); +#endif + + void ConfigureSystemPlatformCreateOptions(ref EOSCreateOptions createOptions); void RegisterForPlatformNotifications(); @@ -61,4 +65,4 @@ public interface IPlatformSpecifics void UpdateNetworkStatus(); #endif } -} +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/PlatformManager.cs b/com.playeveryware.eos/Runtime/Core/PlatformManager.cs index 94846714e..b69b33dc1 100644 --- a/com.playeveryware.eos/Runtime/Core/PlatformManager.cs +++ b/com.playeveryware.eos/Runtime/Core/PlatformManager.cs @@ -27,51 +27,91 @@ namespace PlayEveryWare.EpicOnlineServices #if UNITY_EDITOR using UnityEditor; - #endif +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif using Utility; - public static class PlatformManager + public static partial class PlatformManager { /// /// Enum that stores the possible platforms /// + [Flags] public enum Platform { - Unknown, - Windows, - Android, - XboxOne, - XboxSeriesX, - iOS, - Linux, - macOS, - PS4, - PS5, - Switch, - Steam + Unknown = 0x0, + Windows = 0x1, + Android = 0x2, + XboxOne = 0x4, + XboxSeriesX = 0x8, + iOS = 0x10, + Linux = 0x20, + macOS = 0x40, + PS4 = 0x80, + PS5 = 0x100, + Switch = 0x200, + Steam = 0x400, + Console = PS4 | PS5 | XboxOne | XboxSeriesX | Switch, + Any = Unknown | Windows | Android | XboxOne | XboxSeriesX | iOS | Linux | macOS | PS4 | PS5 | Switch | Steam } - private struct PlatformInfo + private readonly struct PlatformInfo { - public string FullName; - public string ConfigFileName; - public Type ConfigType; - public string DynamicLibraryExtension; + public string FullName { get; } + public string ConfigFileName { get; } + public string DynamicLibraryExtension { get; } + public string PlatformIconLabel { get; } + public Func GetConfigFunction { get; } + public Type ConfigType { get; } + + private PlatformInfo(Func getConfigFunction, Type configType, string fullName, string configFileName, string dynamicLibraryExtension, + string platformIconLabel) + { + FullName = fullName; + ConfigFileName = configFileName; + DynamicLibraryExtension = dynamicLibraryExtension; + PlatformIconLabel = platformIconLabel; + GetConfigFunction = getConfigFunction; + ConfigType = configType; + } + + public static PlatformInfo Create(string fullName, string configFileName, string dynamicLibraryExtension, string platformIconLabel) where T : PlatformConfig + { + return new PlatformInfo(Config.Get, typeof(T), fullName, configFileName, dynamicLibraryExtension, + platformIconLabel); + } } +#if !INCLUDE_RESTRICTED_PLATFORMS /// - /// Private collection to store associations between a platform type, it's human readable name, the file in which the configuration is stored, and the type of the config object + /// Private collection to store information about each platform. /// - private static IDictionary PlatformInformation = - new Dictionary(); + private static readonly IDictionary PlatformInformation = + new Dictionary() + { +#if !EXTERNAL_TO_UNITY + { Platform.Android, PlatformInfo.Create("Android", "eos_android_config.json", null, "Android")}, + { Platform.iOS, PlatformInfo.Create ("iOS", "eos_ios_config.json", null, "iPhone") }, + { Platform.Linux, PlatformInfo.Create ("Linux", "eos_linux_config.json", ".so", "Standalone") }, + { Platform.macOS, PlatformInfo.Create ("macOS", "eos_macos_config.json", ".dylib", "Standalone") }, +#endif + { Platform.Windows, PlatformInfo.Create("Windows", "eos_windows_config.json", ".dll", "Standalone") }, + }; +#endif + + /// + /// Backing value for the CurrentPlatform property. + /// + private static Platform s_CurrentPlatform; /// - /// Backing value for the CurrentPlatform property. + /// Used to cache the PlatformConfig that pertains to the current + /// platform. /// - private static Platform s_CurrentPlatform; + private static PlatformConfig s_platformConfig = null; /// /// Returns the current platform. In-order to reduce the number of places in the build pipeline @@ -99,42 +139,88 @@ public static Platform CurrentPlatform static PlatformManager() { - AddPlatformInfo(Platform.Android, "Android", "eos_android_config.json" ); - AddPlatformInfo(Platform.iOS, "iOS", "eos_ios_config.json" ); - AddPlatformInfo(Platform.Linux, "Linux", "eos_linux_config.json" ); - AddPlatformInfo(Platform.macOS, "macOS", "eos_macos_config.json" ); - AddPlatformInfo(Platform.Steam, "Steam", "eos_steam_config.json" ); - AddPlatformInfo(Platform.XboxOne, "Xbox One", "eos_xb1_config.json" ); - AddPlatformInfo(Platform.XboxSeriesX, "Xbox Series X", "eos_xsx_config.json" ); - AddPlatformInfo(Platform.PS4, "PS4", "eos_ps4_config.json" ); - AddPlatformInfo(Platform.PS5, "PS5", "eos_ps5_config.json" ); - AddPlatformInfo(Platform.Switch, "Switch", "eos_switch_config.json" ); - //// TODO: Currently, there is no special config that is utilized for Windows - instead current implementation simply - //// relies on EpicOnlineServicesConfig.json, so for now this entry is different. What is commented below is what it *should* be to be consistent. - //// AddPlatformInfo(Platform.Windows, "Windows", "eos_windows_config.json", typeof(EOSWindowsConfig), ".dll"); - //// For the time being, this is the entry for the Windows platform - AddPlatformInfo(Platform.Windows, "Windows", "EpicOnlineServicesConfig.json" ); + // If external to unity, then we know that the current platform + // is Windows. +#if EXTERNAL_TO_UNITY + CurrentPlatform = Platform.Windows; +#else + // If the Unity Editor is currently running, then the "active" + // Platform is whatever the current build target is. +#if UNITY_EDITOR + if (TryGetPlatform(EditorUserBuildSettings.activeBuildTarget, out Platform platform)) +#else + // If the Unity editor is _not_ currently running, then the "active" + // platform is whatever the runtime application says it is + if (TryGetPlatform(Application.platform, out Platform platform)) +#endif + { + CurrentPlatform = platform; + } + else + { + CurrentPlatform = Platform.Unknown; + Debug.LogWarning("Platform could not be determined."); + } +#endif } - public static void SetPlatformDetails(Platform platform, Type configType, string dynamicLibraryExtension) + public static PlatformConfig GetPlatformConfig() { - PlatformInfo info = PlatformInformation[platform]; - info.ConfigType = configType; - info.DynamicLibraryExtension = dynamicLibraryExtension; + if (s_platformConfig != null) + { + return s_platformConfig; + } + + if (!PlatformInformation.TryGetValue(CurrentPlatform, out PlatformInfo platformInfo) || null == platformInfo.GetConfigFunction) + { + Debug.LogError($"Could not get platform config for platform \"{CurrentPlatform}\"."); + return null; + } + + s_platformConfig = platformInfo.GetConfigFunction(); - PlatformInformation.Remove(platform); - PlatformInformation.Add(new KeyValuePair(platform, info)); + return s_platformConfig; } - public static void AddPlatformInfo(Platform platform, string fullName, string configFileName) +#if !EXTERNAL_TO_UNITY + /// + /// Maps Unity RuntimePlatform to Platform + /// + private static readonly IDictionary RuntimeToPlatformsMap = + new Dictionary() + { + { RuntimePlatform.Android, Platform.Android}, + { RuntimePlatform.IPhonePlayer, Platform.iOS}, + { RuntimePlatform.PS4, Platform.PS4}, + { RuntimePlatform.PS5, Platform.PS5}, + { RuntimePlatform.GameCoreXboxOne, Platform.XboxOne}, + { RuntimePlatform.XboxOne, Platform.XboxOne}, + { RuntimePlatform.Switch, Platform.Switch}, + { RuntimePlatform.GameCoreXboxSeries, Platform.XboxSeriesX}, + { RuntimePlatform.LinuxPlayer, Platform.Linux}, + { RuntimePlatform.LinuxEditor, Platform.Linux}, + { RuntimePlatform.EmbeddedLinuxX64, Platform.Linux}, + { RuntimePlatform.EmbeddedLinuxX86, Platform.Linux}, + { RuntimePlatform.LinuxServer, Platform.Linux}, + { RuntimePlatform.WindowsServer, Platform.Windows}, + { RuntimePlatform.WindowsPlayer, Platform.Windows}, + { RuntimePlatform.WindowsEditor, Platform.Windows}, + { RuntimePlatform.OSXEditor, Platform.macOS}, + { RuntimePlatform.OSXPlayer, Platform.macOS}, + { RuntimePlatform.OSXServer, Platform.macOS}, + }; + + /// + /// Get the platform that matches the given runtime platform. + /// + /// The active RuntimePlatform + /// The platform for that RuntimePlatform. + /// True if platform was determined, false otherwise. + public static bool TryGetPlatform(RuntimePlatform runtimePlatform, out Platform platform) { - PlatformInformation.Add(new KeyValuePair(platform, - new PlatformInfo() - { - FullName = fullName, - ConfigFileName = configFileName, - })); + return RuntimeToPlatformsMap.TryGetValue(runtimePlatform, out platform); } +#endif #if UNITY_EDITOR /// @@ -152,7 +238,7 @@ public static void AddPlatformInfo(Platform platform, string fullName, string co { BuildTarget.PS5, Platform.PS5 }, { BuildTarget.Switch, Platform.Switch }, { BuildTarget.StandaloneWindows, Platform.Windows }, - { BuildTarget.StandaloneWindows64, Platform.Windows } + { BuildTarget.StandaloneWindows64, Platform.Windows }, }; /// @@ -190,26 +276,43 @@ public static bool TryGetPlatform(BuildTarget target, out Platform platform) return TargetToPlatformsMap.TryGetValue(target, out platform); } + public static bool TryGetConfigType(Platform platform, out Type configType) + { + configType = null; + + bool typeFound = PlatformInformation.TryGetValue(platform, out PlatformInfo value); + + if (typeFound) + { + configType = value.ConfigType; + } + + return typeFound; + } + /// - /// Get the config type for the current platform. + /// Try to retrieve the config file path for the indicated BuildTarget. /// - /// The config type for the current platform. - public static Type GetConfigType() + /// The BuildTarget to get the configuration file for. + /// The filepath to the configuration file. + /// True if there is a config file path for the indicated BuildTarget. + public static bool TryGetConfigFilePath(BuildTarget target, out string configFilePath) { - return GetConfigType(PlatformManager.CurrentPlatform); + var platform = TargetToPlatformsMap[target]; + return TryGetConfigFilePath(platform, out configFilePath); } /// - /// Returns the type of the PlatformConfig that holds configuration values for the indicated Platform. + /// Return the built-in icon for the indicated platform. /// - /// The Platform to get the specific PlatformConfig type of. - /// Type of the specific PlatformConfig that represents the indicated Platform. - public static Type GetConfigType(Platform platform) + /// The platform to get the icon for. + /// An icon texture representing the platform. + public static Texture GetPlatformIcon(Platform platform) { - return PlatformInformation[platform].ConfigType; + return EditorGUIUtility.IconContent($"BuildSettings.{PlatformInformation[platform].PlatformIconLabel}.Small").image; } - #endif + /// /// Return a string that represents the file extension used by the indicated platform for dynamic library files. /// @@ -220,39 +323,6 @@ public static string GetDynamicLibraryExtension(Platform platform) return PlatformInformation[platform].DynamicLibraryExtension; } - - /// - /// Get the file extension used by the current platform for dynamic library files. - /// - /// A string containing the file extension used for dynamic library files on the current platform. - public static string GetDynamicLibraryExtension() - { - return GetDynamicLibraryExtension(CurrentPlatform); - } - -#if UNITY_EDITOR - /// - /// Returns the type of the PlatformConfig that holds configuration values for the indicated BuildTarget - /// - /// The BuildTarget to get the specific PlatformConfig type of. - /// Type of the specific PlatformConfig that represents the indicated Platform. - public static Type GetConfigType(BuildTarget target) - { - Platform platform = TargetToPlatformsMap[target]; - return GetConfigType(platform); - } - - /// - /// Return the fully qualified path to the configuration file for the given build target. - /// - /// The build target to get the configuration file for. - /// Fully qualified path. - private static string GetConfigFilePath(BuildTarget target) - { - var platform = TargetToPlatformsMap[target]; - return GetConfigFilePath(platform); - } -#endif /// /// Return the fully qualified path to the configuration file for the current platform. /// @@ -294,20 +364,6 @@ public static bool TryGetConfigFilePath(Platform platform, out string configFile return false; } -#if UNITY_EDITOR - /// - /// Try to retrieve the config file path for the indicated BuildTarget. - /// - /// The BuildTarget to get the configuration file for. - /// The filepath to the configuration file. - /// True if there is a config file path for the indicated BuildTarget. - public static bool TryGetConfigFilePath(BuildTarget target, out string configFilePath) - { - var platform = TargetToPlatformsMap[target]; - return TryGetConfigFilePath(platform, out configFilePath); - } -#endif - /// /// Returns the name of the JSON file that contains configuration values for the given platform. /// diff --git a/com.playeveryware.eos/Runtime/Core/PlatformSpecifics.cs b/com.playeveryware.eos/Runtime/Core/PlatformSpecifics.cs index 7fd0b33f7..d3a496b24 100644 --- a/com.playeveryware.eos/Runtime/Core/PlatformSpecifics.cs +++ b/com.playeveryware.eos/Runtime/Core/PlatformSpecifics.cs @@ -21,7 +21,7 @@ */ #if !EOS_DISABLE -#if !UNITY_EDITOR +#if !UNITY_EDITOR && !EXTERNAL_TO_UNITY using UnityEngine.Scripting; [assembly: AlwaysLinkAssembly] #endif @@ -29,7 +29,13 @@ namespace PlayEveryWare.EpicOnlineServices { using System; using System.Collections.Generic; + + // The EXTERNAL_TO_UNITY block is here to enable the compilation of this + // code file outside of the context of Unity altogether. +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif + using JsonUtility = Utility.JsonUtility; public abstract class PlatformSpecifics : IPlatformSpecifics where T : PlatformConfig @@ -38,10 +44,9 @@ public abstract class PlatformSpecifics : IPlatformSpecifics where T : Platfo #region Methods for which the functionality is shared (consider these "sealed") - protected PlatformSpecifics(PlatformManager.Platform platform, string dynamicLibraryExtension) + protected PlatformSpecifics(PlatformManager.Platform platform) { - this.Platform = platform; - PlatformManager.SetPlatformDetails(platform, typeof(T), dynamicLibraryExtension); + Platform = platform; } public string GetDynamicLibraryExtension() @@ -58,10 +63,14 @@ public virtual string GetTempDir() return Application.temporaryCachePath; } + // The EXTERNAL_TO_UNITY block is here to enable the compilation of this + // code file outside of the context of Unity altogether. +#if !EXTERNAL_TO_UNITY public virtual void InitializeOverlay(IEOSCoroutineOwner owner) { // default behavior is to take no action. } +#endif public virtual void AddPluginSearchPaths(ref List pluginPaths) { @@ -89,31 +98,20 @@ public virtual bool IsApplicationConstrainedWhenOutOfFocus() // this might be different on future platforms. return false; } + public virtual void ConfigureSystemPlatformCreateOptions(ref EOSCreateOptions createOptions) { ((EOSCreateOptions)createOptions).options.RTCOptions = new(); } + // The EXTERNAL_TO_UNITY block is here to enable the compilation of this + // code file outside of the context of Unity altogether. +#if !EXTERNAL_TO_UNITY public virtual void ConfigureSystemInitOptions(ref EOSInitializeOptions initializeOptionsRef) { - Debug.Log("ConfigureSystemInitOptions"); - - if (initializeOptionsRef is not EOSInitializeOptions initializeOptions) - { - throw new Exception("ConfigureSystemInitOptions: initializeOptions is null!"); - } - - if (initializeOptions.options.OverrideThreadAffinity.HasValue) - { - Debug.Log($"Assigning thread affinity override values for platform \"{Platform}\"."); - var overrideThreadAffinity = initializeOptions.options.OverrideThreadAffinity.Value; - - Config.Get().ConfigureOverrideThreadAffinity(ref overrideThreadAffinity); - - initializeOptions.options.OverrideThreadAffinity = overrideThreadAffinity; - } + // Default implementation is to do nothing } - +#endif /// /// Indicates whether the platform is ready for network activity. /// TODO: Determine where this is used, and why it isn't a boolean. diff --git a/com.playeveryware.eos/Runtime/Core/Utility/ConfigurationUtility.cs b/com.playeveryware.eos/Runtime/Core/Utility/ConfigurationUtility.cs new file mode 100644 index 000000000..023a164ae --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Utility/ConfigurationUtility.cs @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices.Utility +{ + using Epic.OnlineServices; + using Epic.OnlineServices.Platform; + using Extensions; + using System; + using Config = EpicOnlineServices.Config; + +#if !EXTERNAL_TO_UNITY + using UnityEngine; +#endif + + /// + /// This class is used to segregate the code responsible for taking + /// configuration values (as stored in JSON and editable by the user from + /// within the Unity editor) and converting those values into the data + /// structures that the EOS SDK requires to be created and initialized. + /// + /// The primary purpose of doing this - aside from separating the concerns, + /// is to allow efficient testing of the verisimilitude between the native + /// and managed implementations of this process. + /// + public static class ConfigurationUtility + { + /// + /// Get the create options used to make the EOS platform. + /// + /// + /// An object that encapsulates the CreateOptions used for creating the + /// EOS SDK platform. + /// + public static EOSCreateOptions GetEOSCreateOptions() + { + PlatformConfig platformConfig = PlatformManager.GetPlatformConfig(); + ProductConfig productConfig = Config.Get(); + + IPlatformSpecifics platformSpecifics = +#if EXTERNAL_TO_UNITY + new WindowsPlatformSpecifics() +#else + EOSManagerPlatformSpecificsSingleton.Instance +#endif + ; + + EOSCreateOptions platformOptions = new(); + + platformOptions.options.CacheDirectory = platformSpecifics.GetTempDir(); + platformOptions.options.IsServer = platformConfig.isServer; + platformOptions.options.Flags = +#if UNITY_EDITOR + PlatformFlags.LoadingInEditor; +#else + platformConfig.platformOptionsFlags.Unwrap(); +#endif + + if (!platformConfig.clientCredentials.IsEncryptionKeyValid()) + { + Debug.LogError("The encryption key used for the selected client credentials is invalid. Please see your platform configuration."); + } + else + { + platformOptions.options.EncryptionKey = platformConfig.clientCredentials.EncryptionKey; + } + + platformOptions.options.OverrideCountryCode = null; + platformOptions.options.OverrideLocaleCode = null; + platformOptions.options.ProductId = productConfig.ProductId.ToString("N").ToLowerInvariant(); + platformOptions.options.SandboxId = platformConfig.deployment.SandboxId.ToString(); + platformOptions.options.DeploymentId = platformConfig.deployment.DeploymentId.ToString("N").ToLowerInvariant(); + + platformOptions.options.TickBudgetInMilliseconds = platformConfig.tickBudgetInMilliseconds; + + // configData has to serialize to JSON, so it doesn't represent null + // If the value is <= 0, then set it to null, which the EOS SDK will handle by using default of 30 seconds. + platformOptions.options.TaskNetworkTimeoutSeconds = platformConfig.taskNetworkTimeoutSeconds > 0 ? platformConfig.taskNetworkTimeoutSeconds : null; + + platformOptions.options.ClientCredentials = new ClientCredentials + { + ClientId = platformConfig.clientCredentials.ClientId, + ClientSecret = platformConfig.clientCredentials.ClientSecret, + }; + +#if !EXTERNAL_TO_UNITY + +#if !(UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX || UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) + var createIntegratedPlatformOptionsContainerOptions = new Epic.OnlineServices.IntegratedPlatform.CreateIntegratedPlatformOptionsContainerOptions(); + var integratedPlatformOptionsContainer = new Epic.OnlineServices.IntegratedPlatform.IntegratedPlatformOptionsContainer(); + var integratedPlatformOptionsContainerResult = Epic.OnlineServices.IntegratedPlatform.IntegratedPlatformInterface.CreateIntegratedPlatformOptionsContainer(ref createIntegratedPlatformOptionsContainerOptions, out integratedPlatformOptionsContainer); + + if (integratedPlatformOptionsContainerResult != Result.Success) + { + Debug.LogError($"Error creating integrated platform container: {integratedPlatformOptionsContainerResult}"); + } + platformOptions.options.IntegratedPlatformOptionsContainerHandle = integratedPlatformOptionsContainer; +#endif +#endif + + // Note that this function only sets rtcoptions - which is not + // exposed or affected by configuration, so it is not considered + // when being called external to unity. +#if !EXTERNAL_TO_UNITY + platformSpecifics.ConfigureSystemPlatformCreateOptions(ref platformOptions); +#endif + return platformOptions; + } + + /// + /// Get the initialize options used to initialize the EOS SDK after it + /// has been created. + /// + /// + /// An object that encapsulates the initialization options utilized to + /// initialize the EOS SDK after it has been created. + /// + public static EOSInitializeOptions GetEOSInitializeOptions() + { + EOSInitializeOptions initOptions = new() { options = new() }; + + // Get the product config and the platform config + ProductConfig productConfig = Config.Get(); + PlatformConfig platformConfig = PlatformManager.GetPlatformConfig(); + + // Set the product name, version, and override thread affinity + initOptions.options.ProductName = productConfig.ProductName; + initOptions.options.ProductVersion = productConfig.ProductVersion; + initOptions.options.OverrideThreadAffinity = platformConfig.threadAffinity.Unwrap(); + + initOptions.options.AllocateMemoryFunction = IntPtr.Zero; + initOptions.options.ReallocateMemoryFunction = IntPtr.Zero; + initOptions.options.ReleaseMemoryFunction = IntPtr.Zero; + +#if !EXTERNAL_TO_UNITY + IPlatformSpecifics platformSpecifics = EOSManagerPlatformSpecificsSingleton.Instance; + platformSpecifics.ConfigureSystemInitOptions(ref initOptions); +#endif + + // Return; + return initOptions; + } + } +} \ No newline at end of file diff --git a/com.playeveryware.eos/Runtime/Core/Utility/ConfigurationUtility.cs.meta b/com.playeveryware.eos/Runtime/Core/Utility/ConfigurationUtility.cs.meta new file mode 100644 index 000000000..f6bb898c0 --- /dev/null +++ b/com.playeveryware.eos/Runtime/Core/Utility/ConfigurationUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2a80b4f410801c4a9bc43800b725258 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.playeveryware.eos/Runtime/Core/Utility/FileSystemUtility.cs b/com.playeveryware.eos/Runtime/Core/Utility/FileSystemUtility.cs index 6ec9ccea6..7d7dd752d 100644 --- a/com.playeveryware.eos/Runtime/Core/Utility/FileSystemUtility.cs +++ b/com.playeveryware.eos/Runtime/Core/Utility/FileSystemUtility.cs @@ -25,14 +25,17 @@ [assembly: InternalsVisibleTo("com.playeveryware.eos-Editor")] namespace PlayEveryWare.EpicOnlineServices.Utility { - using Extensions; + using Common.Extensions; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; + +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif // This compile conditional exists to ensure that the UnityEngine.Networking // namespace is included when not in editor and when the platform is @@ -506,7 +509,11 @@ public static async Task ReadAllTextAsync(string path) { try { +#if NET_STANDARD_2_0 return await File.ReadAllTextAsync(path); +#else + return await Task.Run(() => File.ReadAllText(path)); +#endif } catch (Exception e) { @@ -541,7 +548,7 @@ public static string ReadAllText(string path) - #endregion +#endregion #region Get File System Entries Functionality @@ -655,7 +662,12 @@ public static async Task WriteFileAsync(string filePath, string content, bool cr CreateDirectory(file.Directory); } +#if !EXTERNAL_TO_UNITY await using StreamWriter writer = new(filePath); +#else + using StreamWriter writer = new(filePath); +#endif + await writer.WriteAsync(content); } @@ -679,7 +691,7 @@ private static void CreateDirectory(DirectoryInfo dInfo) } } - #endregion +#endregion #region Directory and File Exists functionality @@ -713,9 +725,9 @@ private static bool ExistsInternal(string path, bool isDirectory = false) { exists = File.Exists(path); } +#endif return exists; -#endif } public static async Task DirectoryExistsAsync(string path) @@ -754,7 +766,7 @@ private static async Task ExistsInternalAsync(string path, bool isDirector return await Task.FromResult(exists); } - #endregion +#endregion public static void NormalizePath(ref string path) { diff --git a/com.playeveryware.eos/Runtime/Core/Utility/JsonUtility.cs b/com.playeveryware.eos/Runtime/Core/Utility/JsonUtility.cs index 38249315c..528861e55 100644 --- a/com.playeveryware.eos/Runtime/Core/Utility/JsonUtility.cs +++ b/com.playeveryware.eos/Runtime/Core/Utility/JsonUtility.cs @@ -22,39 +22,55 @@ namespace PlayEveryWare.EpicOnlineServices.Utility { +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + using System; /// - /// Contains functions to interact with various json data. + /// Contains functions to interact with various JSON data. /// public static class JsonUtility { + private static readonly JsonSerializerSettings s_serializerSettings = + new() { Converters = new JsonConverter[] + { + new PlayEveryWare.EpicOnlineServices.GuidConverter(), + new StringEnumConverter(), + new VersionConverter(), + } }; + /// /// Tries to parse the given JSON into an object. /// - /// The type to deserialize from the given json. + /// + /// The type to deserialize from the given JSON. /// /// /// The JSON to deserialize from. /// - /// The object to contain the deserialized value. + /// + /// The object to contain the deserialized value. /// /// - /// True if the json was successfully deserialized, false otherwise. + /// True if the JSON was successfully deserialized, false otherwise. /// private static bool TryFromJson(string json, out T obj) { obj = default; try { - obj = UnityEngine.JsonUtility.FromJson(json); + obj = JsonConvert.DeserializeObject(json, s_serializerSettings); return true; } - catch + catch (Exception ex) { Debug.LogError($"Unable to parse object of type " + $"\"{typeof(T).FullName}\" from " + - $"JSON: \"{json}\"."); + $"JSON: \"{json}\". " + + $"Exception: \"{ex.Message}\""); #if UNITY_EDITOR // If running in the context of the Unity editor, then throw the // exception in an effort to "fail loudly" so that the user is @@ -70,21 +86,28 @@ private static bool TryFromJson(string json, out T obj) } /// - /// Convert an object into JSON. + /// Converts an object into JSON. /// - /// The object to serialize into JSON. - /// Whether to make the JSON pretty. + /// + /// The object to serialize into JSON. + /// + /// + /// Whether to make the JSON pretty. + /// /// /// String representation of the given object serialized. /// public static string ToJson(object obj, bool pretty = false) { - return UnityEngine.JsonUtility.ToJson(obj, pretty); + return JsonConvert.SerializeObject( + obj, + pretty ? Formatting.Indented : Formatting.None, + s_serializerSettings); } /// - /// Return an object deserialized from the given json string. If json is - /// invalid, errors will be logged, and an object with default values + /// Returns an object deserialized from the given JSON string. If JSON + /// is invalid, errors will be logged, and an object with default values /// will be returned depending on whether running in editor mode or as a /// standalone. /// @@ -98,12 +121,11 @@ public static T FromJson(string json) // was successful, the value defined by the out variable is // returned to the caller. _ = TryFromJson(json, out T returnObject); - return returnObject; } /// - /// Return an object deserialized from the given json file. + /// Returns an object deserialized from the given JSON file. /// /// The type of object to deserialize. /// The path to the JSON file. @@ -115,40 +137,39 @@ public static T FromJsonFile(string filepath) } /// - /// Overwrites the given json object with values deserialized from the - /// given json string. If json is invalid, errors will be logged and no - /// change will be made to the object. + /// Overwrites the given object's properties with values deserialized + /// from the given JSON string. If JSON is invalid, errors will be + /// logged and no change will be made to the object. /// + /// + /// The type of the object to populate. + /// /// - /// The string of json to deserialize values from. + /// The JSON string to deserialize values from. /// /// /// The object to change the values of. /// public static void FromJsonOverwrite(string json, T obj) { - // NOTES: - // - // If the type 'T' indicated is abstract, then it is not possible to - // use UnityEngine.JsonUtility to verify the validity of the - // incoming JSON. This is because the validation is accomplished by - // performing the deserialization step and catching any thrown - // exceptions. Therefore, if the type indicated is abstract, don't - // bother trying to validate, go straight to using the - // 'FromJsonOverwrite' function in UnityEngine.JsonUtility. - // - // If the type indicated is _not_ abstract, then it is possible to - // utilize the validation approach, therefore in that scenario - // perform validation before performing the intended operation. - // - // That this code can only validate JSON representing a non-abstract - // class is a limitation of the UnityEngine.JsonUtility. Most other - // available libraries will support this and remove the need for - // this check. - if (typeof(T).IsAbstract || TryFromJson(json, out T _)) + if (obj == null) { - UnityEngine.JsonUtility.FromJsonOverwrite(json, obj); + throw new ArgumentNullException(nameof(obj)); + } + try + { + JsonConvert.PopulateObject(json, obj, s_serializerSettings); + } + catch (Exception ex) + { + Debug.LogError($"Unable to populate object of type " + + $"\"{typeof(T).FullName}\" from " + + $"JSON: \"{json}\". " + + $"Exception: \"{ex.Message}\""); +#if UNITY_EDITOR + throw; +#endif } } } -} \ No newline at end of file +} diff --git a/com.playeveryware.eos/Runtime/Core/Utility/LogLevelUtility.cs b/com.playeveryware.eos/Runtime/Core/Utility/LogLevelUtility.cs index 92f51122c..6ec3c9714 100644 --- a/com.playeveryware.eos/Runtime/Core/Utility/LogLevelUtility.cs +++ b/com.playeveryware.eos/Runtime/Core/Utility/LogLevelUtility.cs @@ -26,7 +26,9 @@ namespace PlayEveryWare.EpicOnlineServices.Utility { using System; using System.Collections.Generic; +#if !EXTERNAL_TO_UNITY using UnityEngine; +#endif using Epic.OnlineServices.Logging; public static class LogLevelUtility diff --git a/com.playeveryware.eos/Runtime/EOS_SDK/Core/Config.cs b/com.playeveryware.eos/Runtime/EOS_SDK/Core/Config.cs index e79cba387..19903ef68 100644 --- a/com.playeveryware.eos/Runtime/EOS_SDK/Core/Config.cs +++ b/com.playeveryware.eos/Runtime/EOS_SDK/Core/Config.cs @@ -54,51 +54,57 @@ namespace Epic.OnlineServices // platform support. public static partial class Config { - // This conditional (added by PlayEveryWare) is here (in conjunction with - // the class it is contained within being marked as "partial") so that - // other platforms can be supported by adding to this code-base another - // part of the partial class that sets the LibraryName differently - // depending on the presence of other scripting defines that indicate - // available functionality on other platforms. - #if EOS_PLATFORM_WINDOWS_32 || EOS_PLATFORM_WINDOWS_64 || EOS_PLATFORM_OSX || EOS_PLATFORM_LINUX || EOS_PLATFORM_IOS || EOS_PLATFORM_ANDROID + // This conditional (added by PlayEveryWare) is here (in conjunction with + // the class it is contained within being marked as "partial") so that + // other platforms can be supported by adding to this code-base another + // part of the partial class that sets the LibraryName differently + // depending on the presence of other scripting defines that indicate + // available functionality on other platforms. +#if EOS_PLATFORM_WINDOWS_32 || EOS_PLATFORM_WINDOWS_64 || EOS_PLATFORM_OSX || EOS_PLATFORM_LINUX || EOS_PLATFORM_IOS || EOS_PLATFORM_ANDROID public const string LibraryName = - #if EOS_PLATFORM_WINDOWS_32 && EOS_UNITY +#if EOS_PLATFORM_WINDOWS_32 && EOS_UNITY "EOSSDK-Win32-Shipping" - #elif EOS_PLATFORM_WINDOWS_32 +#elif EOS_PLATFORM_WINDOWS_32 "EOSSDK-Win32-Shipping.dll" - #elif EOS_PLATFORM_WINDOWS_64 && EOS_UNITY +#elif EOS_PLATFORM_WINDOWS_64 && EOS_UNITY "EOSSDK-Win64-Shipping" - #elif EOS_PLATFORM_WINDOWS_64 +#elif EOS_PLATFORM_WINDOWS_64 "EOSSDK-Win64-Shipping.dll" - #elif EOS_PLATFORM_OSX && EOS_UNITY +#elif EOS_PLATFORM_OSX && EOS_UNITY "libEOSSDK-Mac-Shipping" - #elif EOS_PLATFORM_OSX +#elif EOS_PLATFORM_OSX "libEOSSDK-Mac-Shipping.dylib" - #elif EOS_PLATFORM_LINUX && EOS_UNITY +#elif EOS_PLATFORM_LINUX && EOS_UNITY "libEOSSDK-Linux-Shipping" - #elif EOS_PLATFORM_LINUX +#elif EOS_PLATFORM_LINUX "libEOSSDK-Linux-Shipping.so" - #elif EOS_PLATFORM_IOS && EOS_UNITY && EOS_EDITOR +#elif EOS_PLATFORM_IOS && EOS_UNITY && EOS_EDITOR "EOSSDK" - #elif EOS_PLATFORM_IOS +#elif EOS_PLATFORM_IOS "EOSSDK.framework/EOSSDK" - #elif EOS_PLATFORM_ANDROID +#elif EOS_PLATFORM_ANDROID "EOSSDK" - #else - #error Unable to determine the name of the EOSSDK library. Ensure you have set the correct EOS compilation symbol for the current platform, such as EOS_PLATFORM_WINDOWS_32 or EOS_PLATFORM_WINDOWS_64, so that the correct EOSSDK library can be targeted. +#else +#error Unable to determine the name of the EOSSDK library. Ensure you have set the correct EOS compilation symbol for the current platform, such as EOS_PLATFORM_WINDOWS_32 or EOS_PLATFORM_WINDOWS_64, so that the correct EOSSDK library can be targeted. "EOSSDK-UnknownPlatform-Shipping" - #endif +#endif ; - #endif - // PEW: End modify - public const CallingConvention LibraryCallingConvention = +#elif EXTERNAL_TO_UNITY +#if PLATFORM_64 + public const string LibraryName = "EOSSDK-Win64-Shipping.dll"; +#else + public const string LibraryName = "EOSSDK-Win32-Shipping.dll"; +#endif +#endif + // PEW: End modify + public const CallingConvention LibraryCallingConvention = #if EOS_PLATFORM_WINDOWS_32 CallingConvention.StdCall #else diff --git a/com.playeveryware.eos/Runtime/EOS_SDK/Core/Helper.cs b/com.playeveryware.eos/Runtime/EOS_SDK/Core/Helper.cs index 6c46ace37..0db0f84fb 100644 --- a/com.playeveryware.eos/Runtime/EOS_SDK/Core/Helper.cs +++ b/com.playeveryware.eos/Runtime/EOS_SDK/Core/Helper.cs @@ -630,6 +630,16 @@ private static IntPtr AddPinnedBuffer(Utf8String str) return AddPinnedBuffer(str.Bytes, 0); } + // PEW: Start Modify + // This function is added because this file is included in a class + // library with a lower supported version of C# that does not allow a + // byte array to be automatically converted into an ArraySegment + internal static IntPtr AddPinnedBuffer(byte[] array) + { + return array == null ? IntPtr.Zero : AddPinnedBuffer(new ArraySegment(array)); + } + // PEW: End Modify + internal static IntPtr AddPinnedBuffer(ArraySegment array) { if (array == null) diff --git a/com.playeveryware.eos/package.json b/com.playeveryware.eos/package.json index e6b8f15bc..5e2f690e6 100644 --- a/com.playeveryware.eos/package.json +++ b/com.playeveryware.eos/package.json @@ -12,7 +12,7 @@ "description": "Friendly Plugin for Epic Online Services\n Unity 2021.3.16f1\n EOS SDK 1.16.4\n\n Dependencies for Extra Packs:\n P2P Netcode Sample : [com.unity.netcode.gameobjects] \n Performance Stress Test Sample : [com.unity.postprocessing]", "documentationUrl": "https://eospluginforunity.playeveryware.com", "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.editorcoroutines": "1.0.0" }, "keywords": [ diff --git a/etc/EOSImportDesriptions/eos_import_android_description.json b/etc/EOSImportDesriptions/eos_import_android_description.json index 09878388c..5a736a6d8 100644 --- a/etc/EOSImportDesriptions/eos_import_android_description.json +++ b/etc/EOSImportDesriptions/eos_import_android_description.json @@ -4,6 +4,8 @@ {"src":"SDK/Source/Generated/Android/Platform/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Android/Platform/"}, {"src": "SDK/Bin/Android/dynamic-stdc++/aar/*.aar", "dest": "etc/PlatformSpecificAssets/EOS/Android/dynamic-stdc++/aar/"}, - {"src": "SDK/Bin/Android/static-stdc++/aar/*.aar", "dest": "etc/PlatformSpecificAssets/EOS/Android/static-stdc++/aar/"} + {"src": "SDK/Bin/Android/static-stdc++/aar/*.aar", "dest": "etc/PlatformSpecificAssets/EOS/Android/static-stdc++/aar/"}, + + {"src": "SDK/Include/Android/*.h", "dest": "lib/NativeCode/third_party/eos_sdk/include/Android/"} ] } diff --git a/etc/EOSImportDesriptions/eos_import_ios_description.json b/etc/EOSImportDesriptions/eos_import_ios_description.json index a951b4c98..a2971aac0 100644 --- a/etc/EOSImportDesriptions/eos_import_ios_description.json +++ b/etc/EOSImportDesriptions/eos_import_ios_description.json @@ -4,6 +4,8 @@ {"src":"SDK/Source/Generated/iOS/Auth/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/iOS/Auth/"}, {"src": "SDK/Bin/IOS/EOSSDK.framework/EOSSDK", "dest": "Assets/Plugins/iOS/EOSSDK.framework/"}, - {"src": "SDK/Bin/IOS/EOSSDK.framework/Info.plist", "dest": "Assets/Plugins/iOS/EOSSDK.framework/"} + {"src": "SDK/Bin/IOS/EOSSDK.framework/Info.plist", "dest": "Assets/Plugins/iOS/EOSSDK.framework/"}, + + {"src": "SDK/Include/IOS/*.h", "dest": "lib/NativeCode/third_party/eos_sdk/include/IOS/"} ] } diff --git a/etc/EOSImportDesriptions/eos_import_linux_description.json b/etc/EOSImportDesriptions/eos_import_linux_description.json index dd085bbf9..3fa1012b3 100644 --- a/etc/EOSImportDesriptions/eos_import_linux_description.json +++ b/etc/EOSImportDesriptions/eos_import_linux_description.json @@ -1,5 +1,7 @@ { "source_to_dest": [ - {"src": "SDK/Bin/libEOSSDK-Linux-Shipping.so", "dest": "Assets/Plugins/Linux/"} + {"src": "SDK/Bin/libEOSSDK-Linux-Shipping.so", "dest": "Assets/Plugins/Linux/"}, + + {"src": "SDK/Include/Linux/*.h", "dest": "lib/NativeCode/third_party/eos_sdk/include/Linux/"} ] } diff --git a/etc/EOSImportDesriptions/eos_import_mac_description.json b/etc/EOSImportDesriptions/eos_import_mac_description.json index 68a47ba38..cb0661c63 100644 --- a/etc/EOSImportDesriptions/eos_import_mac_description.json +++ b/etc/EOSImportDesriptions/eos_import_mac_description.json @@ -1,5 +1,7 @@ { "source_to_dest": [ - {"src": "SDK/Bin/libEOSSDK-Mac-Shipping.dylib", "dest": "Assets/Plugins/macOS/"} + {"src": "SDK/Bin/libEOSSDK-Mac-Shipping.dylib", "dest": "Assets/Plugins/macOS/"}, + + {"src": "SDK/Include/Mac/*.h", "dest": "lib/NativeCode/third_party/eos_sdk/include/Mac/"} ] } diff --git a/etc/EOSImportDesriptions/eos_import_shared_description.json b/etc/EOSImportDesriptions/eos_import_shared_description.json index c749f9226..8c3ab98a0 100644 --- a/etc/EOSImportDesriptions/eos_import_shared_description.json +++ b/etc/EOSImportDesriptions/eos_import_shared_description.json @@ -34,6 +34,8 @@ {"src":"SDK/Source/Generated/Ui/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Ui/"}, {"src":"SDK/Source/Generated/UserInfo/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/UserInfo/"}, {"src":"SDK/Source/Generated/Version/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Version/"}, - {"src":"SDK/Source/Overrides/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Overrides/"} + {"src":"SDK/Source/Overrides/*.cs", "dest": "com.playeveryware.eos/Runtime/EOS_SDK/Overrides/"}, + + {"src":"SDK/Include/*.h", "dest": "lib/NativeCode/third_party/eos_sdk/include/"} ] } diff --git a/etc/EOSImportDesriptions/eos_import_windows_description.json b/etc/EOSImportDesriptions/eos_import_windows_description.json index 984d69264..82243ae9b 100644 --- a/etc/EOSImportDesriptions/eos_import_windows_description.json +++ b/etc/EOSImportDesriptions/eos_import_windows_description.json @@ -8,6 +8,8 @@ {"src": "SDK/Bin/EOSSDK-Win64-Shipping.dll", "dest": "Assets/Plugins/Windows/x64/"}, {"src": "SDK/Bin/x64/", "pattern": "*.dll", "dest": "Assets/Plugins/Windows/x64/"}, + {"src": "SDK/Include/Windows/*.h", "dest": "lib/NativeCode/third_party/eos_sdk/include/Windows/"}, + {"src": "SDK/Tools/EOSBootstrapper.exe", "dest": "tools/bin/"}, {"src": "SDK/Tools/EOSBootstrapperTool.exe", "dest": "tools/bin/"} ] diff --git a/etc/PackageConfigurations/eos_package_description.json b/etc/PackageConfigurations/eos_package_description.json index 8a9acf095..35c240ff4 100644 --- a/etc/PackageConfigurations/eos_package_description.json +++ b/etc/PackageConfigurations/eos_package_description.json @@ -1,131 +1,65 @@ { - "source_to_dest": [ - - {"src": "com.playeveryware.eos/*.md", "dest": "" }, - {"src": "com.playeveryware.eos/package.json", "dest": "" }, - {"src": "com.playeveryware.eos/Editor.meta", "dest": "" }, - {"src": "com.playeveryware.eos/Runtime.meta", "dest": "" }, - - {"src": "etc/PlatformSpecificAssets/EOS/*", "dest": "PlatformSpecificAssets~/EOS/", "recursive": true }, - {"src": "com.playeveryware.eos/Documentation~/*", "dest": "Documentation~/", "recursive": true }, - {"src": "tools/bin/*", "dest": "bin~/", "recursive": true }, - - {"src": "Assets/EOS.meta", "dest": "Runtime/" }, - {"src": "Assets/EOS/*.png", "dest": "Runtime/EOS/" }, - - {"src": "Assets/Plugins/*", "dest": "Runtime/" }, - {"src": "com.playeveryware.eos/Runtime/*", "dest": "Runtime/" }, - {"src": "com.playeveryware.eos/Runtime/Core/*", "dest": "Runtime/Core/" }, - {"src": "com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/*", "dest": "Runtime/Core/EOS_SDK_Additions/" }, - {"src": "com.playeveryware.eos/Runtime/Core/EOS_SDK_Additions/Extensions/*", "dest": "Runtime/Core/EOS_SDK_Additions/Extensions/" }, - {"src": "com.playeveryware.eos/Runtime/Core/Utility/*", "dest": "Runtime/Core/Utility/" }, - - {"src": "com.playeveryware.eos/Runtime/Core/Extensions/*", "dest": "Runtime/Core/Extensions/" }, - {"src": "com.playeveryware.eos/Runtime/Core/Config/*", "dest": "Runtime/Core/Config/" }, - {"src": "com.playeveryware.eos/Runtime/Core/Config/Attributes/*", "dest": "Runtime/Core/Config/Attributes/" }, - - {"src": "Assets/Plugins/Source/Editor/*", "dest": "Editor/" }, - {"src": "Assets/Plugins/Source/Editor/Build/*", "dest": "Editor/Build/" }, - {"src": "Assets/Plugins/Source/Editor/ConfigEditors/*", "dest": "Editor/ConfigEditors/" }, - {"src": "Assets/Plugins/Source/Editor/Configs/*", "dest": "Editor/Configs/" }, - {"src": "Assets/Plugins/Source/Editor/EditorWindows/*", "dest": "Editor/EditorWindows/" }, - - {"src": "Assets/Plugins/Source/Editor/Utility/*", "dest": "Editor/Utility/", "recursive": true }, - - {"src": "Assets/Plugins/Source/Editor/Platforms/*", "dest": "Editor/Platforms/" }, - {"src": "Assets/Plugins/Source/Editor/Platforms/Android/", "dest": "Editor/Platforms/Android/" }, - {"src": "Assets/Plugins/Source/Editor/Platforms/iOS/", "dest": "Editor/Platforms/iOS/" }, - {"src": "Assets/Plugins/Source/Editor/Platforms/Linux/", "dest": "Editor/Platforms/Linux/" }, - {"src": "Assets/Plugins/Source/Editor/Platforms/macOS/", "dest": "Editor/Platforms/macOS/" }, - {"src": "Assets/Plugins/Source/Editor/Platforms/Standalone/", "dest": "Editor/Platforms/Standalone/" }, - {"src": "Assets/Plugins/Source/Editor/Platforms/Windows/", "dest": "Editor/Platforms/Windows/" }, - - {"src": "Assets/Plugins/Windows/*", "dest": "Runtime/Windows/", "recursive": true }, - - {"ignore_regex": "Assets/Plugins/Windows/.*/steam_api\\.dll" }, - {"ignore_regex": "Assets/Plugins/Windows/.*/steam_api64\\.dll" }, - - {"src": "Assets/Plugins/Linux/*", "dest": "Runtime/Linux/", "recursive": true }, - {"src": "Assets/Plugins/macOS/*", "dest": "Runtime/macOS/", "recursive": true }, - {"src": "Assets/Plugins/Android/*", "dest": "Runtime/Android/", "recursive": true }, - {"src": "Assets/Plugins/iOS/*", "dest": "Runtime/iOS/", "recursive": true }, - - {"src": "Assets/link.xml", "dest": "Editor/"}, - - {"src": "Assets/Plugins/Source/EOS_SDK.meta", "dest": "Runtime/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/*", "dest": "Runtime/EOS_SDK/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Overrides/*", "dest": "Runtime/EOS_SDK/Overrides/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Core/*", "dest": "Runtime/EOS_SDK/Core/" }, - - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/*", "dest": "Runtime/EOS_SDK/Generated/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Achievements/*", "dest": "Runtime/EOS_SDK/Generated/Achievements/" }, - - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Android/*", "dest": "Runtime/EOS_SDK/Generated/Android/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Android/Platform/*", "dest": "Runtime/EOS_SDK/Generated/Android/Platform/" }, - - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/AntiCheatClient/*", "dest": "Runtime/EOS_SDK/Generated/AntiCheatClient/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/AntiCheatCommon/*", "dest": "Runtime/EOS_SDK/Generated/AntiCheatCommon/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/AntiCheatServer/*", "dest": "Runtime/EOS_SDK/Generated/AntiCheatServer/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Auth/*", "dest": "Runtime/EOS_SDK/Generated/Auth/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Connect/*", "dest": "Runtime/EOS_SDK/Generated/Connect/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/CustomInvites/*", "dest": "Runtime/EOS_SDK/Generated/CustomInvites/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Ecom/*", "dest": "Runtime/EOS_SDK/Generated/Ecom/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Friends/*", "dest": "Runtime/EOS_SDK/Generated/Friends/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/IntegratedPlatform/*", "dest": "Runtime/EOS_SDK/Generated/IntegratedPlatform/" }, - - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/IOS/*", "dest": "Runtime/EOS_SDK/Generated/IOS/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/IOS/Auth/*", "dest": "Runtime/EOS_SDK/Generated/IOS/Auth/" }, - - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/KWS/*", "dest": "Runtime/EOS_SDK/Generated/KWS/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Leaderboards/*", "dest": "Runtime/EOS_SDK/Generated/Leaderboards/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Lobby/*", "dest": "Runtime/EOS_SDK/Generated/Lobby/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Logging/*", "dest": "Runtime/EOS_SDK/Generated/Logging/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Metrics/*", "dest": "Runtime/EOS_SDK/Generated/Metrics/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Mods/*", "dest": "Runtime/EOS_SDK/Generated/Mods/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/P2P/*", "dest": "Runtime/EOS_SDK/Generated/P2P/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Platform/*", "dest": "Runtime/EOS_SDK/Generated/Platform/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/PlayerDataStorage/*", "dest": "Runtime/EOS_SDK/Generated/PlayerDataStorage/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Presence/*", "dest": "Runtime/EOS_SDK/Generated/Presence/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/ProgressionSnapshot/*", "dest": "Runtime/EOS_SDK/Generated/ProgressionSnapshot/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Reports/*", "dest": "Runtime/EOS_SDK/Generated/Reports/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/RTC/*", "dest": "Runtime/EOS_SDK/Generated/RTC/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/RTCAdmin/*", "dest": "Runtime/EOS_SDK/Generated/RTCAdmin/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/RTCAudio/*", "dest": "Runtime/EOS_SDK/Generated/RTCAudio/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/RTCData/*", "dest": "Runtime/EOS_SDK/Generated/RTCData/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Sanctions/*", "dest": "Runtime/EOS_SDK/Generated/Sanctions/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Sessions/*", "dest": "Runtime/EOS_SDK/Generated/Sessions/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Stats/*", "dest": "Runtime/EOS_SDK/Generated/Stats/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/TitleStorage/*", "dest": "Runtime/EOS_SDK/Generated/TitleStorage/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Types/*", "dest": "Runtime/EOS_SDK/Generated/Types/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/UI/*", "dest": "Runtime/EOS_SDK/Generated/UI/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/UserInfo/*", "dest": "Runtime/EOS_SDK/Generated/UserInfo/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Version/*", "dest": "Runtime/EOS_SDK/Generated/Version/" }, - - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Windows/*", "dest": "Runtime/EOS_SDK/Generated/Windows/" }, - {"src": "com.playeveryware.eos/Runtime/EOS_SDK/Generated/Windows/Platform/*", "dest": "Runtime/EOS_SDK/Generated/Windows/Platform/" }, - - {"src": "Assets/Resources/SourceCodePro.ttf", "dest": "Samples~/Samples/StandardSamples/Materials/" }, - {"src": "Assets/Prefab/*.prefab", "dest": "Samples~/Samples/StandardSamples/Prefab/" }, - {"src": "Assets/Scripts/*", "dest": "Samples~/Samples/StandardSamples/Scripts/" }, - - {"src": "Assets/Scripts/StandardSamples/", "dest": "Samples~/Samples/StandardSamples/Scripts/", "recursive": true}, - - {"src": "Assets/Scenes/StandardSamples/*.unity", "dest": "Samples~/Samples/StandardSamples/Scenes/" }, - {"src": "Assets/Scenes/SampleScenes.asset", "dest": "Samples~/Samples/StandardSamples/Scenes/" }, - {"src": "Assets/Materials/StandardSamplesMaterials/*", "dest": "Samples~/Samples/StandardSamples/Materials/" }, - - {"src": "Assets/Scenes/P2PNetcodeSample/*.unity", "dest": "Samples~/Samples/P2PNetcodeSample/Scenes/" }, - {"src": "Assets/Materials/NetcodeMaterials/*", "dest": "Samples~/Samples/P2PNetcodeSample/Materials/" }, - {"src": "Assets/Scripts/P2PNetcodeSample/*", "dest": "Samples~/Samples/P2PNetcodeSample/Scripts/", "recursive": true }, - - {"src": "Assets/Scenes/PerformanceStressTestSample/*.unity", "dest": "Samples~/Samples/PerformanceStressTestSample/Scenes/" }, - {"src": "Assets/Materials/PerformanceStressTestMaterials/*", "dest": "Samples~/Samples/PerformanceStressTestSample/Materials/" }, - {"src": "Assets/Scripts/PerformanceStressTestSample/*", "dest": "Samples~/Samples/PerformanceStressTestSample/Scripts/", "recursive": true }, - {"src": "Assets/Resources/PerformanceStressTest_Profiles/*", "dest": "Samples~/Samples/PerformanceStressTestSample/Profiles/" }, - - {"src": "Assets/Readme/*.txt", "dest": "Samples~/Samples/StandardSamples/Readme/" } - ], - "blacklist": [ - "*.png" - ] + "source_to_dest": [ + + {"src": "etc/PlatformSpecificAssets/EOS/*", "dest": "PlatformSpecificAssets~/EOS/", "recursive": true }, + {"src": "tools/bin/*", "dest": "bin~/", "recursive": true }, + + {"src": "Assets/EOS.meta", "dest": "Runtime/" }, + {"src": "Assets/EOS/*.png", "dest": "Runtime/EOS/" }, + + {"src": "Assets/Plugins/*", "dest": "Runtime/" }, + {"src": "com.playeveryware.eos/*", "dest": "", "recursive": true }, + + {"src": "Assets/Plugins/Source/Editor/*", "dest": "Editor/" }, + {"src": "Assets/Plugins/Source/Editor/Build/*", "dest": "Editor/Build/" }, + {"src": "Assets/Plugins/Source/Editor/ConfigEditors/*", "dest": "Editor/ConfigEditors/" }, + {"src": "Assets/Plugins/Source/Editor/Configs/*", "dest": "Editor/Configs/" }, + {"src": "Assets/Plugins/Source/Editor/EditorWindows/*", "dest": "Editor/EditorWindows/" }, + + {"src": "Assets/Plugins/Source/Editor/Utility/*", "dest": "Editor/Utility/", "recursive": true }, + + {"src": "Assets/Plugins/Source/Editor/Platforms/*", "dest": "Editor/Platforms/" }, + {"src": "Assets/Plugins/Source/Editor/Platforms/Android/", "dest": "Editor/Platforms/Android/" }, + {"src": "Assets/Plugins/Source/Editor/Platforms/iOS/", "dest": "Editor/Platforms/iOS/" }, + {"src": "Assets/Plugins/Source/Editor/Platforms/Linux/", "dest": "Editor/Platforms/Linux/" }, + {"src": "Assets/Plugins/Source/Editor/Platforms/macOS/", "dest": "Editor/Platforms/macOS/" }, + {"src": "Assets/Plugins/Source/Editor/Platforms/Standalone/", "dest": "Editor/Platforms/Standalone/" }, + {"src": "Assets/Plugins/Source/Editor/Platforms/Windows/", "dest": "Editor/Platforms/Windows/" }, + + {"src": "Assets/Plugins/Windows/*", "dest": "Runtime/Windows/", "recursive": true }, + {"src": "Assets/Plugins/Linux/*", "dest": "Runtime/Linux/", "recursive": true }, + {"src": "Assets/Plugins/macOS/*", "dest": "Runtime/macOS/", "recursive": true }, + {"src": "Assets/Plugins/Android/*", "dest": "Runtime/Android/", "recursive": true }, + {"src": "Assets/Plugins/iOS/*", "dest": "Runtime/iOS/", "recursive": true }, + + {"src": "Assets/link.xml", "dest": "Editor/" }, + {"src": "Assets/Plugins/Source/EOS_SDK.meta", "dest": "Runtime/" }, + + {"src": "Assets/Resources/SourceCodePro.ttf", "dest": "Samples~/Samples/StandardSamples/Materials/" }, + {"src": "Assets/Prefab/*.prefab", "dest": "Samples~/Samples/StandardSamples/Prefab/" }, + {"src": "Assets/Scripts/*", "dest": "Samples~/Samples/StandardSamples/Scripts/" }, + + {"src": "Assets/Scripts/StandardSamples/", "dest": "Samples~/Samples/StandardSamples/Scripts/", "recursive": true }, + + {"src": "Assets/Scenes/StandardSamples/*.unity", "dest": "Samples~/Samples/StandardSamples/Scenes/" }, + {"src": "Assets/Scenes/SampleScenes.asset", "dest": "Samples~/Samples/StandardSamples/Scenes/" }, + {"src": "Assets/Materials/StandardSamplesMaterials/*", "dest": "Samples~/Samples/StandardSamples/Materials/" }, + + {"src": "Assets/Scenes/P2PNetcodeSample/*.unity", "dest": "Samples~/Samples/P2PNetcodeSample/Scenes/" }, + {"src": "Assets/Materials/NetcodeMaterials/*", "dest": "Samples~/Samples/P2PNetcodeSample/Materials/" }, + {"src": "Assets/Scripts/P2PNetcodeSample/*", "dest": "Samples~/Samples/P2PNetcodeSample/Scripts/", "recursive": true }, + + {"src": "Assets/Scenes/PerformanceStressTestSample/*.unity", "dest": "Samples~/Samples/PerformanceStressTestSample/Scenes/" }, + {"src": "Assets/Materials/PerformanceStressTestMaterials/*", "dest": "Samples~/Samples/PerformanceStressTestSample/Materials/" }, + {"src": "Assets/Scripts/PerformanceStressTestSample/*", "dest": "Samples~/Samples/PerformanceStressTestSample/Scripts/", "recursive": true }, + {"src": "Assets/Resources/PerformanceStressTest_Profiles/*", "dest": "Samples~/Samples/PerformanceStressTestSample/Profiles/" }, + + {"src": "Assets/Readme/*.txt", "dest": "Samples~/Samples/StandardSamples/Readme/" }, + + {"ignore_regex": "Assets/Plugins/Windows/.*/steam_api\\.dll" }, + {"ignore_regex": "Assets/Plugins/Windows/.*/steam_api64\\.dll" } + ], + "blacklist": [ + "*.png" + ] } \ No newline at end of file diff --git a/etc/config/eos_plugin_android_build_config.json b/etc/config/eos_plugin_android_build_config.json index 5c0f675d1..c1406ebf4 100644 --- a/etc/config/eos_plugin_android_build_config.json +++ b/etc/config/eos_plugin_android_build_config.json @@ -1,3 +1,4 @@ { - "DynamicallyLinkEOSLibrary": false + "DynamicallyLinkEOSLibrary": false, + "schemaVersion": "1.0" } \ No newline at end of file diff --git a/etc/config/eos_plugin_library_build_config.json b/etc/config/eos_plugin_library_build_config.json index 4b3edea0b..fd8a1c587 100644 --- a/etc/config/eos_plugin_library_build_config.json +++ b/etc/config/eos_plugin_library_build_config.json @@ -1,5 +1,6 @@ { - "msbuildPath": "", - "makePath": "", - "msbuildDebug": false + "msbuildPath": "", + "makePath": "", + "msbuildDebug": false, + "schemaVersion": "1.0" } \ No newline at end of file diff --git a/etc/config/eos_plugin_signing_config.json b/etc/config/eos_plugin_signing_config.json index 461d5a5e8..2a11c1d5e 100644 --- a/etc/config/eos_plugin_signing_config.json +++ b/etc/config/eos_plugin_signing_config.json @@ -1,7 +1,8 @@ { - "pathToSignTool": "", - "pathToPFX": "", - "pfxPassword": "", - "timestampURL": "", - "dllPaths": [] + "pathToSignTool": "", + "pathToPFX": "", + "pfxPassword": "", + "timestampURL": "", + "dllPaths": [], + "schemaVersion": "1.0" } \ No newline at end of file diff --git a/etc/config/eos_plugin_steam_config.json b/etc/config/eos_plugin_steam_config.json new file mode 100644 index 000000000..fac1be61d --- /dev/null +++ b/etc/config/eos_plugin_steam_config.json @@ -0,0 +1,8 @@ +{ + "overrideLibraryPath": null, + "steamSDKMajorVersion": 0, + "steamSDKMinorVersion": 0, + "steamApiInterfaceVersionsArray": [], + "integratedPlatformManagementFlags": "Disabled", + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/etc/config/eos_plugin_tools_config.json b/etc/config/eos_plugin_tools_config.json index b4249d578..6a47027f9 100644 --- a/etc/config/eos_plugin_tools_config.json +++ b/etc/config/eos_plugin_tools_config.json @@ -1,10 +1,11 @@ { - "pathToEACIntegrityTool": "", - "pathToEACIntegrityConfig": "", - "pathToDefaultCertificate": "", - "pathToEACPrivateKey": "", - "pathToEACCertificate": "", - "pathToEACSplashImage": "", - "bootstrapperNameOverride": "", - "useEAC": false + "pathToEACIntegrityTool": "", + "pathToEACIntegrityConfig": "", + "pathToDefaultCertificate": "", + "pathToEACPrivateKey": "", + "pathToEACCertificate": "", + "pathToEACSplashImage": "", + "bootstrapperNameOverride": "", + "useEAC": false, + "schemaVersion": "1.0" } \ No newline at end of file diff --git a/etc/config/eos_plugin_version_config.json b/etc/config/eos_plugin_version_config.json index 9e4a5adcb..541be4481 100644 --- a/etc/config/eos_plugin_version_config.json +++ b/etc/config/eos_plugin_version_config.json @@ -1,3 +1,4 @@ { - "useAppVersionAsProductVersion": false + "useAppVersionAsProductVersion": false, + "schemaVersion": "1.0" } \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/.gitignore b/lib/NativeCode/DynamicLibraryLoaderHelper/.gitignore new file mode 100644 index 000000000..faf48f0b9 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/.gitignore @@ -0,0 +1,3 @@ +# Exclude nuget packages +packages/* +Build/* \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/.gitignore b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/.gitignore new file mode 100644 index 000000000..338c2b020 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/.gitignore @@ -0,0 +1,2 @@ +x64/* +*.user \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.cpp new file mode 100644 index 000000000..f5f1bf9fe --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#include "include/config_legacy.h" + +int main() +{ + pew::eos::config_legacy::EOSConfig eos_config; + if(pew::eos::config_legacy::try_get_eos_config(eos_config)) + { + std::cout << "EOSConfig was read successfully."; + } + else + { + std::cout << "Could not load EOSConfig."; + } +} + +// Run program: Ctrl + F5 or Debug > Start Without Debugging menu +// Debug program: F5 or Debug > Start Debugging menu + +// Tips for Getting Started: +// 1. Use the Solution Explorer window to add/manage files +// 2. Use the Team Explorer window to connect to source control +// 3. Use the Output window to see build output and other messages +// 4. Use the Error List window to view errors +// 5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project +// 6. In the future, to open this project again, go to File > Open > Project and select the .sln file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.vcxproj b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.vcxproj new file mode 100644 index 000000000..82fa07774 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.vcxproj @@ -0,0 +1,179 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {d7462eaf-0024-4ea5-9d89-dbbbc7766333} + ConsoleApplication + 10.0 + + + + false + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\Temp\$(ProjectName)\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\$(Configuration)\$(Platform)\;$(LibraryPath) + $(ProjectName)-$(PlatformTarget) + + + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\Temp\$(ProjectName)\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\$(Configuration)\$(Platform)\;$(LibraryPath) + $(ProjectName)-$(PlatformTarget) + + + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\Temp\$(ProjectName)\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\$(Configuration)\$(Platform)\;$(LibraryPath) + $(ProjectName)-$(PlatformTarget) + + + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\Temp\$(ProjectName)\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\$(Configuration)\$(Platform)\;$(LibraryPath) + $(ProjectName)-$(PlatformTarget) + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)NativeRender\;$(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\GfxPluginNativeRender-$(PlatformTarget).lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)NativeRender\;$(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\GfxPluginNativeRender-$(PlatformTarget).lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)NativeRender\;$(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\GfxPluginNativeRender-$(PlatformTarget).lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)NativeRender\;$(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\GfxPluginNativeRender-$(PlatformTarget).lib;%(AdditionalDependencies) + + + + + + + + {251d4477-4c90-4fe1-94a2-52377327d3b2} + + + + + + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.vcxproj.filters b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.vcxproj.filters new file mode 100644 index 000000000..ed610da4e --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ConsoleApplication/ConsoleApplication.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/Directory.Build.props b/lib/NativeCode/DynamicLibraryLoaderHelper/Directory.Build.props new file mode 100644 index 000000000..c604b03ec --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/Directory.Build.props @@ -0,0 +1,25 @@ + + + + x86 + x64 + x86 + x64 + + + $(SolutionDir.Replace('\', '/')) + $(SolutionDirForwardSlashes)../../../Assets/StreamingAssets/ + + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.sln b/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.sln index 9e145432e..1c3db1a7d 100644 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.sln +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.sln @@ -7,6 +7,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DynamicLibraryLoaderHelper" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GfxPluginNativeRender", "NativeRender\NativeRender.vcxproj", "{251D4477-4C90-4FE1-94A2-52377327D3B2}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApplication", "ConsoleApplication\ConsoleApplication.vcxproj", "{D7462EAF-0024-4EA5-9D89-DBBBC7766333}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedPluginCode", "ManagedPluginCode\ManagedPluginCode.csproj", "{B3ADEB07-B668-482E-A5AA-9671CE65C5F7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -31,6 +35,18 @@ Global {251D4477-4C90-4FE1-94A2-52377327D3B2}.Release|x64.Build.0 = Release|x64 {251D4477-4C90-4FE1-94A2-52377327D3B2}.Release|x86.ActiveCfg = Release|Win32 {251D4477-4C90-4FE1-94A2-52377327D3B2}.Release|x86.Build.0 = Release|Win32 + {D7462EAF-0024-4EA5-9D89-DBBBC7766333}.Debug|x64.ActiveCfg = Debug|x64 + {D7462EAF-0024-4EA5-9D89-DBBBC7766333}.Debug|x64.Build.0 = Debug|x64 + {D7462EAF-0024-4EA5-9D89-DBBBC7766333}.Debug|x86.ActiveCfg = Debug|Win32 + {D7462EAF-0024-4EA5-9D89-DBBBC7766333}.Debug|x86.Build.0 = Debug|Win32 + {D7462EAF-0024-4EA5-9D89-DBBBC7766333}.Release|x64.ActiveCfg = Release|x64 + {D7462EAF-0024-4EA5-9D89-DBBBC7766333}.Release|x86.ActiveCfg = Release|Win32 + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7}.Debug|x64.ActiveCfg = Debug|x64 + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7}.Debug|x64.Build.0 = Debug|x64 + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7}.Debug|x86.ActiveCfg = Debug|x86 + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7}.Debug|x86.Build.0 = Debug|x86 + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7}.Release|x64.ActiveCfg = Release|x64 + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7}.Release|x86.ActiveCfg = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj b/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj index db1285804..eafe0b68c 100644 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj @@ -26,75 +26,42 @@ 10.0.17763.0 - + + StaticLibrary true v141 Unicode + true - + DynamicLibrary false v141 true Unicode + false - - StaticLibrary - true - v141 - Unicode - - - DynamicLibrary - false - v141 - true - Unicode + + + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\ + $(SolutionDir)Build\Temp\$(ProjectName)\$(Configuration)\$(PlatformTarget)\ + $(ProjectName)-$(PlatformTarget) - - - - - - - - - - - - - - + + + + - - true - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - - - true - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - - - false - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - $(ProjectName)-x86 - - - false - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - $(ProjectName)-x64 - - + + Use Level3 - Disabled true - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true pch.h $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) @@ -105,72 +72,33 @@ true - + + - Use - Level3 Disabled - true _DEBUG;_LIB;%(PreprocessorDefinitions) - true - pch.h - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) - stdcpp17 - - Windows - true - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - true - pch.h - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) - MultiThreaded - stdcpp17 - - - Windows - true - true - true - - - copy $(SolutionDir)$(ProjectName)-x86.dll.meta $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - - + - Use - Level3 MaxSpeed true true - true NDEBUG;_LIB;%(PreprocessorDefinitions) - true - pch.h - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) - stdcpp17 + MultiThreaded - Windows true true - true - copy $(SolutionDir)$(ProjectName)-x64.dll.meta $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ + + copy $(SolutionDir)$(ProjectName)-$(NormalizedPlatform).dll.meta $(OutputUnityAssetsDirectory) /Y + + copy "$(TargetDir)$(ProjectName)-$(NormalizedPlatform).dll" "$(OutputUnityAssetsDirectory)" /Y + @@ -182,15 +110,11 @@ - Create - Create - Create - Create + Create - - + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj.filters b/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj.filters index 934524dfe..8a309942b 100644 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj.filters +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.vcxproj.filters @@ -32,9 +32,6 @@ - - Source Files - Source Files @@ -47,5 +44,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/.gitignore b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/.gitignore new file mode 100644 index 000000000..3dea4cee7 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/.gitignore @@ -0,0 +1,2 @@ +bin/* +obj/* \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Application.cs b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Application.cs new file mode 100644 index 000000000..fe7d3eae8 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Application.cs @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + /// + /// This class is defined here as a stand-in for UnityEngine.Application, + /// so that files compiled outside of the Unity Editor that reference that + /// class can still be compiled properly. + /// + internal static class Application + { + /// + /// This is the path of the streaming assets directory relative to the + /// output directory of this class library. + /// + public static readonly string streamingAssetsPath = @"..\..\..\..\Assets\StreamingAssets\"; + + /// + /// For the purposes of this class library, this field member must be + /// present - but it's value is not utilized, so it is being left empty. + /// + public static readonly string temporaryCachePath = string.Empty; + } +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Debug.cs b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Debug.cs new file mode 100644 index 000000000..f2663e92c --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Debug.cs @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace PlayEveryWare.EpicOnlineServices +{ + using System; + + /// + /// + /// This class is defined here as a stand-in for UnityEngine.Debug, so that + /// files compiled outside of the Unity Editor that reference that class can + /// still be compiled properly. + /// + /// + /// In this class library, when Debug.Log* functions are called, the "log" + /// message is output to the console - not to any log file or other logging + /// system. + /// + /// + /// It is internal because it will only ever be utilized within this class + /// library. + /// + /// + internal static class Debug + { + /// + /// Base functionality common to all the log functions. + /// + /// The message to write to the console. + private static void LogBase(string message) + { + Console.WriteLine(message); + } + + /// + /// Logs the given message to the console. + /// + /// Message to log. + public static void LogError(string message) + { + LogBase($"ERROR: {message}"); + } + + /// + /// Logs the given message to the console. + /// + /// Message to log. + public static void LogWarning(string message) + { + LogBase($"WARNING: {message}"); + } + + /// + /// Logs the given message to the console. + /// + /// Message to log. + public static void Log(string message) + { + LogBase($"INFO: {message}"); + } + + public static void LogException(Exception e) + { + LogBase($"EXCEPTION: \"{e.Message}\"."); + } + } +} \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/ManagedPluginCode.csproj b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/ManagedPluginCode.csproj new file mode 100644 index 000000000..846f3fcb9 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/ManagedPluginCode.csproj @@ -0,0 +1,223 @@ + + + + + Debug + AnyCPU + {B3ADEB07-B668-482E-A5AA-9671CE65C5F7} + Library + Properties + ManagedPluginCode + ManagedPluginCode + v4.8 + 512 + true + $(UnityStreamingAssetsDirectory) + + + true + full + false + + + pdbonly + true + + + 9.0 + TRACE;DEBUG;EXTERNAL_TO_UNITY + prompt + 4 + $(SolutionDir)Build\$(Configuration)\$(Platform) + + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + Assets\Plugins\Windows\Core\WindowsConfig.cs + + + Assets\Plugins\Windows\Core\WindowsPlatformSpecifics.cs + + + com.playeveryware.eos\Runtime\Core\Common\Extensions\FileInfoExtensions.cs + + + com.playeveryware.eos\Runtime\Core\Common\Extensions\ListExtensions.cs + + + com.playeveryware.eos\Runtime\Core\Utility\EnumUtility.cs + + + com.playeveryware.eos\Runtime\Core\Utility\HashUtility.cs + + + com.playeveryware.eos\Runtime\Core\Common\Extensions\StringExtensions.cs + + + com.playeveryware.eos\Runtime\Core\Common\Named.cs + + + com.playeveryware.eos\Runtime\Core\Common\SetOfNamed.cs + + + com.playeveryware.eos\Runtime\Core\Common\VersionUtility.cs + + + com.playeveryware.eos\Runtime\Core\Common\ValueChangedEventArgs.cs + + + com.playeveryware.eos\Runtime\Core\Common\Wrapped.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\ButtonFieldAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\ConfigFieldAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\ConfigFieldType.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\ConfigGroupAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\DirectoryPathFieldAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\ExpandFieldAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\FieldValidator.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\FieldValidatorAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\FilePathFieldAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\GUIDFieldValidatorAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\NonEmptyStringFieldValidatorAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\PlatformDependentConfigFieldAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Attributes\SandboxIDFieldValidatorAttribute.cs + + + com.playeveryware.eos\Runtime\Core\Config\Config.cs + + + com.playeveryware.eos\Runtime\Core\Config\Deployment.cs + + + com.playeveryware.eos\Runtime\Core\Config\EOSConfig.cs + + + com.playeveryware.eos\Runtime\Core\Config\JsonConverter\GuidConverter.cs + + + com.playeveryware.eos\Runtime\Core\Config\JsonConverter\ListOfStringsToEnumConverters.cs + + + com.playeveryware.eos\Runtime\Core\Config\JsonConverter\StringToTypeConverter.cs + + + com.playeveryware.eos\Runtime\Core\Config\LogLevelConfig.cs + + + com.playeveryware.eos\Runtime\Core\Config\PlatformConfig.cs + + + com.playeveryware.eos\Runtime\Core\Config\ProductConfig.cs + + + com.playeveryware.eos\Runtime\Core\Config\ProductionEnvironments.cs + + + com.playeveryware.eos\Runtime\Core\Config\RuntimeConfig.cs + + + com.playeveryware.eos\Runtime\Core\Config\SandboxId.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\EOSClientCredentials.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\EventHandlers.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\Extensions\AuthScopeFlagsExtensions.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\Extensions\IntegratedPlatformManagementFlagsExtensions.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\Extensions\PlatformFlagsExtensions.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\FileRequestTransferWrapper.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\IFileTransferRequest.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\PlayerDataStorageFileTransferRequestWrapper.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\TitleStorageFileTransferRequestWrapper.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\WrappedInitializeThreadAffinity.cs + + + com.playeveryware.eos\Runtime\Core\EOS_SDK_Additions\WrappedPlatformFlags.cs + + + com.playeveryware.eos\Runtime\Core\IPlatformSpecifics.cs + + + com.playeveryware.eos\Runtime\Core\PlatformManager.cs + + + com.playeveryware.eos\Runtime\Core\PlatformSpecifics.cs + + + com.playeveryware.eos\Runtime\Core\Utility\ConfigurationUtility.cs + + + com.playeveryware.eos\Runtime\Core\Utility\FileSystemUtility.cs + + + com.playeveryware.eos\Runtime\Core\Utility\JsonUtility.cs + + + com.playeveryware.eos\Runtime\Core\Utility\LogLevelUtility.cs + + + com.playeveryware.eos\Runtime\EOS_SDK\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Properties/AssemblyInfo.cs b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..071af5d46 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ManagedPluginCode")] +[assembly: AssemblyDescription("Contains managed implementation from within the Unity project EOS Plugin for Unity.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("PlayEveryWare, Inc.")] +[assembly: AssemblyProduct("EOS Plugin for Unity")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("en-US")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b3adeb07-b668-482e-a5aa-9671ce65c5f7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/packages.config b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/packages.config new file mode 100644 index 000000000..0b14af3ad --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/ManagedPluginCode/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/ConfigPaths.props b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/ConfigPaths.props new file mode 100644 index 000000000..03703bcb3 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/ConfigPaths.props @@ -0,0 +1,30 @@ + + + + $([System.String]::Copy('$(MSBuildThisFileDirectory)').Replace('\', '/'))../../../../Assets/StreamingAssets/EOS/ + + + + $([System.String]::Copy('$(MSBuildThisFileDirectory)').Replace('\', '/'))../../StreamingAssets/EOS/ + + + + EOSSDK-Win64-Shipping.dll + EOSOVH-Win64-Shipping.dll + steam_api64.dll + + + + EOSSDK-Win32-Shipping.dll + EOSOVH-Win32-Shipping.dll + steam_api.dll + + + + + $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(NormalizedPlatform)\ + + diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj index e3683cb98..5c1fef38c 100644 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj @@ -27,81 +27,46 @@ GfxPluginNativeRender - + + DynamicLibrary true v141 Unicode + true - + DynamicLibrary false v141 - true - Unicode - - - DynamicLibrary - true - v141 Unicode - - - DynamicLibrary - false - v141 true - Unicode + false + + + + $(SolutionDir)Build\$(Configuration)\$(PlatformTarget)\ + $(ProjectName)-$(PlatformTarget) + $(SolutionDir)Build\Temp\$(ProjectName)\$(Configuration)\$(PlatformTarget)\ - - - - - - - - - - - - - - + + + + - - true - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - $(ProjectName)-x64 - - - true - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - $(ProjectName)-x86 - - - false - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - $(ProjectName)-x86 - - - false - $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - $(ProjectName)-x64 - - + + Use Level3 - Disabled true - _DEBUG;NATIVERENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true pch.h stdcpp17 - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + $(ProjectDir)include\;$(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) Windows @@ -109,90 +74,60 @@ false - + + - Use - Level3 Disabled - true - WIN32;_DEBUG;NATIVERENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + PEW_EOS_EXPORT;OVERLAY_DLL_NAME="$(OverlayDllName)";STEAM_API_DLL="$(SteamApiDll)";SDK_DLL_NAME="$(SdkDllName)";CONFIG_DIRECTORY="$(ConfigDirectory)";_DEBUG;NATIVERENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - - Windows - true - false - - + - Use - Level3 MaxSpeed true true - true - WIN32;NDEBUG;NATIVERENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) + PEW_EOS_EXPORT;OVERLAY_DLL_NAME="$(OverlayDllName)";STEAM_API_DLL="$(SteamApiDll)";SDK_DLL_NAME="$(SdkDllName)";CONFIG_DIRECTORY="$(ConfigDirectory)";NDEBUG;NATIVERENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) MultiThreaded - Windows - true - true - true - false - - - copy $(SolutionDir)$(ProjectName)-x86.dll.meta $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;NATIVERENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - $(SolutionDir)..\third_party\eos_sdk\include\;$(SolutionDir)..\include\windows;$(ProjectDir);%(AdditionalIncludeDirectories) - - - Windows true true - true - false - copy $(SolutionDir)$(ProjectName)-x64.dll.meta $(SolutionDir)..\..\..\Assets\Plugins\Windows\$(PlatformTarget)\ + + copy $(SolutionDir)$(ProjectName)-$(NormalizedPlatform).dll.meta $(OutputUnityAssetsDirectory) /Y + + copy "$(TargetDir)$(ProjectName)-$(NormalizedPlatform).dll" "$(OutputUnityAssetsDirectory)" /Y + - - + + + + + + + + + + - - Create - Create - Create - Create + Create + + + + + + + + - - + + \ No newline at end of file diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj.filters b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj.filters index 28fa2a3b2..aa75b22e0 100644 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj.filters +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/NativeRender.vcxproj.filters @@ -15,21 +15,62 @@ - + + Header Files - + Header Files - + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + Header Files - + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + Source Files - + Source Files diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/dllmain.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/dllmain.cpp deleted file mode 100644 index 7a78a9ec9..000000000 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/dllmain.cpp +++ /dev/null @@ -1,1745 +0,0 @@ -/* - * Copyright (c) 2021 PlayEveryWare - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -// dllmain.cpp : Defines the entry point for the DLL application. -// This file does some *magick* to load the EOS Overlay DLL. -// This is apparently needed so that the Overlay can render properly -#include "pch.h" - -#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -//#include "eos_minimum_includes.h" -#if PLATFORM_WINDOWS -#include "Windows/eos_Windows_base.h" -#include "Windows/eos_Windows.h" -#include "processenv.h" -#include -#endif -#include "eos_sdk.h" -#include "eos_logging.h" - -#include "json.h" - -// This define exists because UWP -// Originally, this would load the library with the name as shipped by the .zip file -#define USE_PLATFORM_WITHOUT_BITS 0 - -#if USE_PLATFORM_WITHOUT_BITS -#define DLL_PLATFORM "-Win" -#else -#if PLATFORM_64BITS -#define DLL_PLATFORM "-Win64" -#else -#define DLL_PLATFORM "-Win32" -#endif -#endif - -#if PLATFORM_64BITS -#define STEAM_DLL_NAME "steam_api64.dll" -#else -#define STEAM_DLL_NAME "steam_api.dll" -#endif - -#define DLL_SUFFIX "-Shipping.dll" - -#define SHOW_DIALOG_BOX_ON_WARN 0 -#define ENABLE_DLL_BASED_EOS_CONFIG 1 -#define OVERLAY_DLL_NAME "EOSOVH" DLL_PLATFORM DLL_SUFFIX -#define SDK_DLL_NAME "EOSSDK" DLL_PLATFORM DLL_SUFFIX -#define XAUDIO2_DLL_NAME "xaudio2_9redist.dll" - -#define EOS_SERVICE_CONFIG_FILENAME "EpicOnlineServicesConfig.json" -#define EOS_STEAM_CONFIG_FILENAME "eos_steam_config.json" -#define EOS_LOGLEVEL_CONFIG_FILENAME "log_level_config.json" - -#define RESTRICT __restrict - -#define DLL_EXPORT(return_value) extern "C" __declspec(dllexport) return_value __stdcall - -namespace fs = std::filesystem; -typedef HKEY__* HKEY; - -using FSig_ApplicationWillShutdown = void (__stdcall *)(void); -FSig_ApplicationWillShutdown FuncApplicationWillShutdown = nullptr; - -typedef const char* (*GetConfigAsJSONString_t)(); - -//------------------------------------------------------------------------- -// Fetched out of DLLs -typedef EOS_EResult(EOS_CALL* EOS_Initialize_t)(const EOS_InitializeOptions* Options); -typedef EOS_EResult(EOS_CALL* EOS_Shutdown_t)(); -typedef EOS_HPlatform(EOS_CALL* EOS_Platform_Create_t)(const EOS_Platform_Options* Options); -typedef void (EOS_CALL* EOS_Platform_Release_t)(EOS_HPlatform Handle); -typedef EOS_EResult (EOS_CALL *EOS_Logging_SetLogLevel_t)(EOS_ELogCategory LogCategory, EOS_ELogLevel LogLevel); -typedef EOS_EResult (EOS_CALL *EOS_Logging_SetCallback_t)(EOS_LogMessageFunc Callback); - -typedef EOS_EResult (*EOS_IntegratedPlatformOptionsContainer_Add_t)(EOS_HIntegratedPlatformOptionsContainer Handle, const EOS_IntegratedPlatformOptionsContainer_AddOptions* InOptions); -typedef EOS_EResult (*EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_t)(const EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainerOptions* Options, EOS_HIntegratedPlatformOptionsContainer* OutIntegratedPlatformOptionsContainerHandle); -typedef void (*EOS_IntegratedPlatformOptionsContainer_Release_t)(EOS_HIntegratedPlatformOptionsContainer IntegratedPlatformOptionsContainerHandle); - -static EOS_Initialize_t EOS_Initialize_ptr; -static EOS_Shutdown_t EOS_Shutdown_ptr; -static EOS_Platform_Create_t EOS_Platform_Create_ptr; -static EOS_Platform_Release_t EOS_Platform_Release_ptr; -static EOS_Logging_SetLogLevel_t EOS_Logging_SetLogLevel_ptr; -static EOS_Logging_SetCallback_t EOS_Logging_SetCallback_ptr; - -static EOS_IntegratedPlatformOptionsContainer_Add_t EOS_IntegratedPlatformOptionsContainer_Add_ptr; -static EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_t EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr; -static EOS_IntegratedPlatformOptionsContainer_Release_t EOS_IntegratedPlatformOptionsContainer_Release_ptr; - -static void *s_eos_sdk_overlay_lib_handle; -static void *s_eos_sdk_lib_handle; -static EOS_HPlatform eos_platform_handle; -static GetConfigAsJSONString_t GetConfigAsJSONString; - -struct SandboxDeploymentOverride -{ - std::string sandboxID; - std::string deploymentID; -}; - -struct EOSConfig -{ - std::string productName; - std::string productVersion; - - std::string productID; - std::string sandboxID; - std::string deploymentID; - std::vector sandboxDeploymentOverrides; - - std::string clientSecret; - std::string clientID; - std::string encryptionKey; - - std::string overrideCountryCode; - std::string overrideLocaleCode; - - // this is called platformOptionsFlags in C# - uint64_t flags = 0; - - uint32_t tickBudgetInMilliseconds = 0; - double taskNetworkTimeoutSeconds = 0.0; - - uint64_t ThreadAffinity_networkWork = 0; - uint64_t ThreadAffinity_storageIO = 0; - uint64_t ThreadAffinity_webSocketIO = 0; - uint64_t ThreadAffinity_P2PIO = 0; - uint64_t ThreadAffinity_HTTPRequestIO = 0; - uint64_t ThreadAffinity_RTCIO = 0; - - bool isServer = false; - -}; - -struct LogLevelConfig -{ - std::vector category; - std::vector level; -}; - -struct EOSSteamConfig -{ - EOS_EIntegratedPlatformManagementFlags flags; - uint32_t steamSDKMajorVersion; - uint32_t steamSDKMinorVersion; - std::optional OverrideLibraryPath; - std::vector steamApiInterfaceVersionsArray; - - EOSSteamConfig() - { - flags = static_cast(0); - } - - bool isManagedByApplication() - { - return std::underlying_type::type(flags & EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication); - } - bool isManagedBySDK() - { - return std::underlying_type::type(flags & EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK); - } - -}; - -extern "C" -{ - void __declspec(dllexport) __stdcall UnityPluginLoad(void* unityInterfaces); - void __declspec(dllexport) __stdcall UnityPluginUnload(); -} - -//------------------------------------------------------------------------- -static bool create_timestamp_str(char *final_timestamp, size_t final_timestamp_len) -{ - constexpr size_t buffer_len = 32; - char buffer[buffer_len]; - - if (buffer_len > final_timestamp_len) - { - return false; - } - - time_t raw_time = time(NULL); - tm time_info = { 0 }; - - timespec time_spec = { 0 }; - timespec_get(&time_spec, TIME_UTC); - localtime_s(&time_info, &raw_time); - - strftime(buffer, buffer_len, "%Y-%m-%dT%H:%M:%S", &time_info); - long milliseconds = (long)round(time_spec.tv_nsec / 1.0e6); - snprintf(final_timestamp, final_timestamp_len, "%s.%03ld", buffer, milliseconds); - - return true; -} - -//------------------------------------------------------------------------- -size_t utf8_str_bytes_required_for_wide_str(const wchar_t* wide_str, int wide_str_len = -1) -{ - int bytes_required = WideCharToMultiByte(CP_UTF8, 0, wide_str, wide_str_len, NULL, 0, NULL, NULL); - - if (bytes_required < 0) - { - return 0; - } - - return bytes_required; -} - -//------------------------------------------------------------------------- -// wide_str must be null terminated if wide_str_len is passed -static bool copy_to_utf8_str_from_wide_str(char* RESTRICT utf8_str, size_t utf8_str_len, const wchar_t* RESTRICT wide_str, int wide_str_len = -1) -{ - if (utf8_str_len > INT_MAX) - { - return false; - } - - WideCharToMultiByte(CP_UTF8, 0, wide_str, wide_str_len, utf8_str, (int)utf8_str_len, NULL, NULL); - - return true; -} - -//------------------------------------------------------------------------- -static char* create_utf8_str_from_wide_str(const wchar_t *wide_str) -{ - const int wide_str_len = (int)wcslen(wide_str) + 1; - int bytes_required = (int)utf8_str_bytes_required_for_wide_str(wide_str, wide_str_len); - char *to_return = (char*)malloc(bytes_required); - - if (!copy_to_utf8_str_from_wide_str(to_return, bytes_required, wide_str, wide_str_len)) - { - free(to_return); - to_return = NULL; - } - - return to_return; -} - -//------------------------------------------------------------------------- -static wchar_t* create_wide_str_from_utf8_str(const char* utf8_str) -{ - int chars_required = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0); - wchar_t *to_return = (wchar_t*)malloc(chars_required * sizeof(wchar_t)); - int utf8_str_len = (int)strlen(utf8_str); - - MultiByteToWideChar(CP_UTF8, 0, utf8_str, utf8_str_len, to_return, chars_required); - - return to_return; -} - -//------------------------------------------------------------------------- -// Using the std::wstring_convert method for this currently. It might be the -// case that in the future this method won't work. If that happens, -// one could convert this function to use the create_utf8_str_from_wide_str -// function to emulate it. Doing this might come with a cost, as data will -// need to be copied multiple times. -static std::string to_utf8_str(const std::wstring& wide_str) -{ - std::wstring_convert> converter; - std::string utf8_str = converter.to_bytes(wide_str); - - return utf8_str; - -} - -//------------------------------------------------------------------------- -// Using fs::path:string().c_str() seems to cause an issue when paths have -// kanji in them. Using this function and then std::string:c_str() works around that -// issue -static std::string to_utf8_str(const fs::path& path) -{ - return to_utf8_str(path.native()); -} - -//------------------------------------------------------------------------- -static uint64_t json_value_as_uint64(json_value_s *value, uint64_t default_value = 0) -{ - uint64_t val = 0; - json_number_s *n = json_value_as_number(value); - - if (n != nullptr) - { - char *end = nullptr; - val = strtoull(n->number, &end, 10); - } - else - { - // try to treat it as a string, then parse as long - char *end = nullptr; - json_string_s* val_as_str = json_value_as_string(value); - if (val_as_str == nullptr || strlen(val_as_str->string) == 0) - { - val = default_value; - } - else - { - val = strtoull(val_as_str->string, &end, 10); - } - } - - return val; -} - -//------------------------------------------------------------------------- -static uint32_t json_value_as_uint32(json_value_s* value, uint32_t default_value = 0) -{ - uint32_t val = 0; - json_number_s* n = json_value_as_number(value); - - if (n != nullptr) - { - char* end = nullptr; - val = strtoul(n->number, &end, 10); - } - else - { - // try to treat it as a string, then parse as long - char* end = nullptr; - json_string_s* val_as_str = json_value_as_string(value); - - if (val_as_str == nullptr || strlen(val_as_str->string) == 0) - { - val = default_value; - } - else - { - val = strtoul(val_as_str->string, &end, 10); - } - } - - return val; -} - -//------------------------------------------------------------------------- -static double json_value_as_double(json_value_s* value, double default_value = 0.0) -{ - double val = 0.0; - json_number_s* n = json_value_as_number(value); - - if (n != nullptr) - { - char* end = nullptr; - val = strtod(n->number, &end); - } - else - { - // try to treat it as a string, then parse as long - char* end = nullptr; - json_string_s* val_as_str = json_value_as_string(value); - - if (val_as_str == nullptr || strlen(val_as_str->string) == 0) - { - val = default_value; - } - else - { - val = strtod(val_as_str->string, &end); - } - } - - return val; -} - -static const char* pick_if_32bit_else(const char* choice_if_32bit, const char* choice_if_else) -{ -#if PLATFORM_32BITS - return choice_if_32bit; -#else - return choice_if_else; -#endif -} - -//------------------------------------------------------------------------- -static const char* eos_loglevel_to_print_str(EOS_ELogLevel level) -{ - switch (level) - { - case EOS_ELogLevel::EOS_LOG_Off: - return "Off"; - break; - case EOS_ELogLevel::EOS_LOG_Fatal: - return "Fatal"; - break; - case EOS_ELogLevel::EOS_LOG_Error: - return "Error"; - break; - case EOS_ELogLevel::EOS_LOG_Warning: - return "Warning"; - break; - case EOS_ELogLevel::EOS_LOG_Info: - return "Info"; - break; - case EOS_ELogLevel::EOS_LOG_Verbose: - return "Verbose"; - break; - case EOS_ELogLevel::EOS_LOG_VeryVerbose: - return "VeryVerbose"; - break; - default: - return nullptr; - } -} - -std::unordered_map const loglevel_str_map = -{ - {"Off",EOS_ELogLevel::EOS_LOG_Off}, - {"Fatal",EOS_ELogLevel::EOS_LOG_Fatal}, - {"Error",EOS_ELogLevel::EOS_LOG_Error}, - {"Warning",EOS_ELogLevel::EOS_LOG_Warning}, - {"Info",EOS_ELogLevel::EOS_LOG_Info}, - {"Verbose",EOS_ELogLevel::EOS_LOG_Verbose}, - {"VeryVerbose",EOS_ELogLevel::EOS_LOG_VeryVerbose}, -}; - -EOS_ELogLevel eos_loglevel_str_to_enum(const std::string& str) -{ - auto it = loglevel_str_map.find(str); - if (it != loglevel_str_map.end()) - { - return it->second; - } - else - { - return EOS_ELogLevel::EOS_LOG_Verbose; - } -} - -//------------------------------------------------------------------------- -static void show_log_as_dialog(const char* log_string) -{ -#if PLATFORM_WINDOWS - MessageBoxA(NULL, log_string, "Warning", MB_ICONWARNING); -#endif -} - -//------------------------------------------------------------------------- -static FILE* log_file_s = nullptr; -static std::vector buffered_output; -void global_log_close() -{ - if (log_file_s) - { - fclose(log_file_s); - log_file_s = nullptr; - buffered_output.clear(); - } -} - -//------------------------------------------------------------------------- -void global_logf(const char* format, ...) -{ - if (log_file_s != nullptr) - { - va_list arg_list; - va_start(arg_list, format); - vfprintf(log_file_s, format, arg_list); - va_end(arg_list); - - fprintf(log_file_s, "\n"); - fflush(log_file_s); - } - else - { - va_list arg_list; - va_start(arg_list, format); - va_list arg_list_copy; - va_copy(arg_list_copy, arg_list); - const size_t printed_length = vsnprintf(nullptr, 0, format, arg_list) + 1; - va_end(arg_list); - - std::vector buffer(printed_length); - vsnprintf(buffer.data(), printed_length, format, arg_list_copy); - va_end(arg_list_copy); - buffered_output.emplace_back(std::string(buffer.data(), printed_length)); - } -} - -//------------------------------------------------------------------------- -void global_log_open(const char* filename) -{ - if (log_file_s != nullptr) - { - fclose(log_file_s); - log_file_s = nullptr; - } - fopen_s(&log_file_s, filename, "w"); - - if (buffered_output.size() > 0) - { - for (const std::string& str : buffered_output) - { - global_logf(str.c_str()); - } - buffered_output.clear(); - } -} - -typedef void (*log_flush_function_t)(const char* str); -DLL_EXPORT(void) global_log_flush_with_function(log_flush_function_t log_flush_function) -{ - if (buffered_output.size() > 0) - { - for (const std::string& str : buffered_output) - { - log_flush_function(str.c_str()); - } - buffered_output.clear(); - } -} - -//------------------------------------------------------------------------- -void log_base(const char* header, const char* message) -{ - constexpr size_t final_timestamp_len = 32; - char final_timestamp[final_timestamp_len] = { }; - if (create_timestamp_str(final_timestamp, final_timestamp_len)) - { - global_logf("%s NativePlugin (%s): %s", final_timestamp, header, message); - } - else - { - global_logf("NativePlugin (%s): %s", header, message); - } -} - -//------------------------------------------------------------------------- -// TODO: If possible, hook this up into a proper logging channel.s -void log_warn(const char* log_string) -{ -#if SHOW_DIALOG_BOX_ON_WARN - show_log_as_dialog(log_string); -#endif - log_base("WARNING", log_string); -} - -//------------------------------------------------------------------------- -void log_inform(const char* log_string) -{ - log_base("INFORM", log_string); -} - -//------------------------------------------------------------------------- -void log_error(const char* log_string) -{ - log_base("ERROR", log_string); -} - -//------------------------------------------------------------------------- -EXTERN_C void EOS_CALL eos_log_callback(const EOS_LogMessage* message) -{ - constexpr size_t final_timestamp_len = 32; - char final_timestamp[final_timestamp_len] = {0}; - - if (create_timestamp_str(final_timestamp, final_timestamp_len)) - { - global_logf("%s %s (%s): %s", final_timestamp, message->Category, eos_loglevel_to_print_str(message->Level), message->Message); - } - else - { - global_logf("%s (%s): %s", message->Category, eos_loglevel_to_print_str(message->Level), message->Message); - } - -} - -//------------------------------------------------------------------------- -static const char* null_if_empty(const std::string& str) -{ - return str.empty() ? nullptr : str.c_str(); -} - -//------------------------------------------------------------------------- -static TCHAR* get_path_to_module(HMODULE module) -{ - DWORD module_path_length = 128; - TCHAR* module_path = (TCHAR*)malloc(module_path_length * sizeof(TCHAR)); - - DWORD buffer_length = 0; - DWORD GetModuleFileName_last_error = 0; - - do { - buffer_length = GetModuleFileName(module, module_path, module_path_length); - GetModuleFileName_last_error = GetLastError(); - SetLastError(NOERROR); - - if (GetModuleFileName_last_error == ERROR_INSUFFICIENT_BUFFER) - { - buffer_length = 0; - module_path_length += 20; - module_path = (TCHAR*)realloc(module_path, module_path_length * sizeof(TCHAR)); - } - } while (buffer_length == 0); - - return module_path; -} - -//------------------------------------------------------------------------- -static std::wstring get_path_to_module_as_string(HMODULE module) -{ - wchar_t* module_path = get_path_to_module(module); - - std::wstring module_file_path_string(module_path); - free(module_path); - return module_file_path_string; -} - -//------------------------------------------------------------------------- -static fs::path get_path_relative_to_current_module(const fs::path& relative_path) -{ - HMODULE this_module = nullptr; - if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)&get_path_relative_to_current_module, &this_module) || !this_module) - { - return {}; - } - - std::wstring module_file_path_string = get_path_to_module_as_string(this_module); - - return fs::path(module_file_path_string).remove_filename() / relative_path; -} - - -//------------------------------------------------------------------------- -static void* load_library_at_path(const std::filesystem::path& library_path) -{ - void* to_return = nullptr; - -#if PLATFORM_WINDOWS - log_inform(("Loading path at " + to_utf8_str(library_path)).c_str()); - HMODULE handle = LoadLibrary(library_path.c_str()); - to_return = (void*)handle; -#endif - - return to_return; -} - -//------------------------------------------------------------------------- -static void* load_function_with_name(void* library_handle, const char* function) -{ - void* to_return = nullptr; -#if PLATFORM_WINDOWS - HMODULE handle = (HMODULE)library_handle; - to_return = (void*)GetProcAddress(handle, function); -#endif - return to_return; -} - -//------------------------------------------------------------------------- -template -T load_function_with_name(void* library_handle, const char* function) -{ - return reinterpret_cast(load_function_with_name(library_handle, function)); -} - -//------------------------------------------------------------------------- -void unload_library(void* library_handle) -{ - FreeLibrary((HMODULE)library_handle); -} - -//------------------------------------------------------------------------- -void eos_init(const EOSConfig& eos_config) -{ - static int reserved[2] = {1, 1}; - EOS_InitializeOptions SDKOptions = { 0 }; - SDKOptions.ApiVersion = EOS_INITIALIZE_API_LATEST; - SDKOptions.AllocateMemoryFunction = nullptr; - SDKOptions.ReallocateMemoryFunction = nullptr; - SDKOptions.ReleaseMemoryFunction = nullptr; - SDKOptions.ProductName = eos_config.productName.c_str(); - SDKOptions.ProductVersion = eos_config.productVersion.c_str(); - SDKOptions.Reserved = reserved; - SDKOptions.SystemInitializeOptions = nullptr; - - EOS_Initialize_ThreadAffinity overrideThreadAffinity = {0}; - - overrideThreadAffinity.ApiVersion = EOS_INITIALIZE_THREADAFFINITY_API_LATEST; - - overrideThreadAffinity.HttpRequestIo = eos_config.ThreadAffinity_HTTPRequestIO; - overrideThreadAffinity.NetworkWork = eos_config.ThreadAffinity_networkWork; - overrideThreadAffinity.P2PIo = eos_config.ThreadAffinity_P2PIO; - overrideThreadAffinity.RTCIo = eos_config.ThreadAffinity_RTCIO; - overrideThreadAffinity.StorageIo = eos_config.ThreadAffinity_storageIO; - overrideThreadAffinity.WebSocketIo = eos_config.ThreadAffinity_webSocketIO; - - - SDKOptions.OverrideThreadAffinity = &overrideThreadAffinity; - - log_inform("call EOS_Initialize"); - EOS_EResult InitResult = EOS_Initialize_ptr(&SDKOptions); - if (InitResult != EOS_EResult::EOS_Success) - { - log_error("Unable to do eos init"); - } - if (EOS_Logging_SetLogLevel_ptr != nullptr) - { - EOS_Logging_SetLogLevel_ptr(EOS_ELogCategory::EOS_LC_ALL_CATEGORIES, EOS_ELogLevel::EOS_LOG_VeryVerbose); - } - - if (EOS_Logging_SetCallback_ptr != nullptr) - { - EOS_Logging_SetCallback_ptr(&eos_log_callback); - } -} - -//------------------------------------------------------------------------- -static char* GetCacheDirectory() -{ - static char* s_tempPathBuffer = NULL; - - if (s_tempPathBuffer == NULL) - { - WCHAR tmp_buffer = 0; - DWORD buffer_size = GetTempPathW(1, &tmp_buffer) + 1; - WCHAR* lpTempPathBuffer = (TCHAR*)malloc(buffer_size * sizeof(TCHAR)); - GetTempPathW(buffer_size, lpTempPathBuffer); - - s_tempPathBuffer = create_utf8_str_from_wide_str(lpTempPathBuffer); - free(lpTempPathBuffer); - } - - return s_tempPathBuffer; -} - -//------------------------------------------------------------------------- -static json_value_s* read_config_json_as_json_from_path(std::filesystem::path path_to_config_json) -{ - log_inform(("json path" + to_utf8_str(path_to_config_json)).c_str()); - uintmax_t config_file_size = std::filesystem::file_size(path_to_config_json); - if (config_file_size > SIZE_MAX) - { - throw std::filesystem::filesystem_error("File is too large", std::make_error_code(std::errc::file_too_large)); - } - - FILE* file = nullptr; - errno_t config_file_error = _wfopen_s(&file, path_to_config_json.wstring().c_str(), L"r"); - char* buffer = (char*)calloc(1, static_cast(config_file_size)); - - size_t bytes_read = fread(buffer, 1, static_cast(config_file_size), file); - fclose(file); - struct json_value_s* config_json = json_parse(buffer, bytes_read); - free(buffer); - - return config_json; -} - -//------------------------------------------------------------------------- -static json_value_s* read_config_json_from_dll() -{ - struct json_value_s* config_json = nullptr; - -#if ENABLE_DLL_BASED_EOS_CONFIG - log_inform("Trying to load eos config via dll"); - static void *eos_generated_library_handle = load_library_at_path(get_path_relative_to_current_module("EOSGenerated.dll")); - - if (!eos_generated_library_handle) - { - log_warn("No Generated DLL found (Might not be an error)"); - return NULL; - } - - GetConfigAsJSONString = load_function_with_name(eos_generated_library_handle, "GetConfigAsJSONString"); - - if(GetConfigAsJSONString) - { - const char* config_as_json_string = GetConfigAsJSONString(); - if (config_as_json_string != nullptr) - { - size_t config_as_json_string_length = strlen(config_as_json_string); - config_json = json_parse(config_as_json_string, config_as_json_string_length); - } - } - else - { - log_warn("No function found"); - } -#endif - - return config_json; -} - -//------------------------------------------------------------------------- -static EOSConfig eos_config_from_json_value(json_value_s* config_json) -{ - // Create platform instance - struct json_object_s* config_json_object = json_value_as_object(config_json); - struct json_object_element_s* iter = config_json_object->start; - EOSConfig eos_config; - - while (iter != nullptr) - { - if (!strcmp("productName", iter->name->string)) - { - eos_config.productName = json_value_as_string(iter->value)->string; - } - else if (!strcmp("productVersion", iter->name->string)) - { - eos_config.productVersion = json_value_as_string(iter->value)->string; - } - else if (!strcmp("productID", iter->name->string)) - { - eos_config.productID = json_value_as_string(iter->value)->string; - } - else if (!strcmp("sandboxID", iter->name->string)) - { - eos_config.sandboxID = json_value_as_string(iter->value)->string; - } - else if (!strcmp("deploymentID", iter->name->string)) - { - eos_config.deploymentID = json_value_as_string(iter->value)->string; - } - else if (!strcmp("sandboxDeploymentOverrides", iter->name->string)) - { - json_array_s* overrides = json_value_as_array(iter->value); - eos_config.sandboxDeploymentOverrides = std::vector(); - for (auto e = overrides->start; e != nullptr; e = e->next) - { - struct json_object_s* override_json_object = json_value_as_object(e->value); - struct json_object_element_s* ov_iter = override_json_object->start; - struct SandboxDeploymentOverride override_item = SandboxDeploymentOverride(); - while (ov_iter != nullptr) - { - if (!strcmp("sandboxID", ov_iter->name->string)) - { - override_item.sandboxID = json_value_as_string(ov_iter->value)->string; - } - else if (!strcmp("deploymentID", ov_iter->name->string)) - { - override_item.deploymentID = json_value_as_string(ov_iter->value)->string; - } - ov_iter = ov_iter->next; - } - eos_config.sandboxDeploymentOverrides.push_back(override_item); - } - } - else if (!strcmp("clientID", iter->name->string)) - { - eos_config.clientID = json_value_as_string(iter->value)->string; - } - else if (!strcmp("clientSecret", iter->name->string)) - { - eos_config.clientSecret = json_value_as_string(iter->value)->string; - } - if (!strcmp("encryptionKey", iter->name->string)) - { - eos_config.encryptionKey = json_value_as_string(iter->value)->string; - } - else if (!strcmp("overrideCountryCode ", iter->name->string)) - { - eos_config.overrideCountryCode = json_value_as_string(iter->value)->string; - } - else if (!strcmp("overrideLocaleCode", iter->name->string)) - { - eos_config.overrideLocaleCode = json_value_as_string(iter->value)->string; - } - else if (!strcmp("platformOptionsFlags", iter->name->string)) - { - uint64_t collected_flags = 0; - json_array_s* flags = json_value_as_array(iter->value); - for (auto e = flags->start; e != nullptr; e = e->next) - { - const char* flag_as_cstr = json_value_as_string(e->value)->string; - - if (!strcmp("EOS_PF_LOADING_IN_EDITOR", flag_as_cstr) || !strcmp("LoadingInEditor", flag_as_cstr)) - { - collected_flags |= EOS_PF_LOADING_IN_EDITOR; - } - - if (!strcmp("EOS_PF_DISABLE_OVERLAY", flag_as_cstr) || !strcmp("DisableOverlay", flag_as_cstr)) - { - collected_flags |= EOS_PF_DISABLE_OVERLAY; - } - - if (!strcmp("EOS_PF_DISABLE_SOCIAL_OVERLAY", flag_as_cstr) || !strcmp("DisableSocialOverlay", flag_as_cstr)) - { - collected_flags |= EOS_PF_DISABLE_SOCIAL_OVERLAY; - } - - if (!strcmp("EOS_PF_RESERVED1", flag_as_cstr) || !strcmp("Reserved1", flag_as_cstr)) - { - collected_flags |= EOS_PF_RESERVED1; - } - - if (!strcmp("EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D9", flag_as_cstr) || !strcmp("WindowsEnableOverlayD3D9", flag_as_cstr)) - { - collected_flags |= EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D9; - } - - if (!strcmp("EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D10", flag_as_cstr) || !strcmp("WindowsEnableOverlayD3D10", flag_as_cstr)) - { - collected_flags |= EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D10; - } - - if (!strcmp("EOS_PF_WINDOWS_ENABLE_OVERLAY_OPENGL", flag_as_cstr) || !strcmp("WindowsEnableOverlayOpengl", flag_as_cstr)) - { - collected_flags |= EOS_PF_WINDOWS_ENABLE_OVERLAY_OPENGL; - } - } - - eos_config.flags = collected_flags; - } - else if (!strcmp("tickBudgetInMilliseconds", iter->name->string)) - { - eos_config.tickBudgetInMilliseconds = json_value_as_uint32(iter->value); - } - else if (!strcmp("taskNetworkTimeoutSeconds", iter->name->string)) - { - eos_config.taskNetworkTimeoutSeconds = json_value_as_double(iter->value); - } - else if (!strcmp("ThreadAffinity_networkWork", iter->name->string)) - { - eos_config.ThreadAffinity_networkWork = json_value_as_uint64(iter->value); - } - else if (!strcmp("ThreadAffinity_storageIO", iter->name->string)) - { - eos_config.ThreadAffinity_storageIO = json_value_as_uint64(iter->value); - } - else if (!strcmp("ThreadAffinity_webSocketIO", iter->name->string)) - { - eos_config.ThreadAffinity_webSocketIO = json_value_as_uint64(iter->value); - } - else if (!strcmp("ThreadAffinity_P2PIO", iter->name->string)) - { - eos_config.ThreadAffinity_P2PIO = json_value_as_uint64(iter->value); - } - else if (!strcmp("ThreadAffinity_HTTPRequestIO", iter->name->string)) - { - eos_config.ThreadAffinity_HTTPRequestIO = json_value_as_uint64(iter->value); - } - else if (!strcmp("ThreadAffinity_RTCIO", iter->name->string)) - { - eos_config.ThreadAffinity_RTCIO = json_value_as_uint64(iter->value); - } - else if (!strcmp("isServer", iter->name->string)) - { - // In this JSON library, true and false are _technically_ different types. - if (json_value_is_true(iter->value)) - { - eos_config.isServer = true; - } - else if (json_value_is_false(iter->value)) - { - eos_config.isServer = false; - } - } - - iter = iter->next; - } - - return eos_config; -} - -static LogLevelConfig log_config_from_json_value(json_value_s* config_json) -{ - struct json_object_s* config_json_object = json_value_as_object(config_json); - struct json_object_element_s* iter = config_json_object->start; - LogLevelConfig log_config; - - while (iter != nullptr) - { - if (!strcmp("LogCategoryLevelPairs", iter->name->string)) - { - json_array_s* pairs = json_value_as_array(iter->value); - for (auto e = pairs->start; e != nullptr; e = e->next) - { - struct json_object_s* pairs_json_object = json_value_as_object(e->value); - struct json_object_element_s* pairs_iter = pairs_json_object->start; - while (pairs_iter != nullptr) - { - if (!strcmp("Category", pairs_iter->name->string)) - { - log_config.category.push_back(json_value_as_string(pairs_iter->value)->string); - } - else if (!strcmp("Level", pairs_iter->name->string)) - { - log_config.level.push_back(json_value_as_string(pairs_iter->value)->string); - } - pairs_iter = pairs_iter->next; - } - } - } - iter = iter->next; - } - - return log_config; -} - -//------------------------------------------------------------------------- -static bool str_is_equal_to_any(const char* str, ...) -{ - bool to_return = false; - va_list arg_list; - va_start(arg_list, str); - - const char *value = va_arg(arg_list, const char*); - - while (value != NULL) - { - if (!strcmp(str, value)) - { - to_return = true; - break; - } - value = va_arg(arg_list, const char*); - } - - va_end(arg_list); - - return to_return; -} - -//------------------------------------------------------------------------- -static bool str_is_equal_to_none(const char* str, ...) -{ - bool to_return = false; - va_list arg_list; - va_start(arg_list, str); - - const char *value = va_arg(arg_list, const char*); - - while (value != NULL) - { - if (strcmp(str, value)) - { - to_return = true; - break; - } - value = va_arg(arg_list, const char*); - } - - va_end(arg_list); - - return to_return; -} - -//------------------------------------------------------------------------- -static EOS_EIntegratedPlatformManagementFlags eos_collect_integrated_platform_managment_flags(json_object_element_s* iter) -{ - EOS_EIntegratedPlatformManagementFlags collected_flags = static_cast(0); - json_array_s* flags = json_value_as_array(iter->value); - bool flag_set = false; - for (auto e = flags->start; e != nullptr; e = e->next) - { - const char* flag_as_cstr = json_value_as_string(e->value)->string; - - if (str_is_equal_to_any(flag_as_cstr, "EOS_IPMF_Disabled", "Disabled", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_Disabled; - flag_set = true; - } - - else if (str_is_equal_to_any(flag_as_cstr, "EOS_IPMF_ManagedByApplication", "ManagedByApplication", "EOS_IPMF_LibraryManagedByApplication", "LibraryManagedByApplication", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication; - flag_set = true; - } - else if (str_is_equal_to_any(flag_as_cstr,"EOS_IPMF_ManagedBySDK", "ManagedBySDK", "EOS_IPMF_LibraryManagedBySDK", "LibraryManagedBySDK", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK; - flag_set = true; - } - else if (str_is_equal_to_any(flag_as_cstr, "EOS_IPMF_DisableSharedPresence", "DisableSharedPresence", "EOS_IPMF_DisablePresenceMirroring", "DisablePresenceMirroring", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisablePresenceMirroring; - flag_set = true; - } - else if (str_is_equal_to_any(flag_as_cstr, "EOS_IPMF_DisableSessions", "DisableSessions", "EOS_IPMF_DisableSDKManagedSessions", "DisableSDKManagedSessions", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisableSDKManagedSessions; - flag_set = true; - } - else if (str_is_equal_to_any(flag_as_cstr, "EOS_IPMF_PreferEOS", "PreferEOS", "EOS_IPMF_PreferEOSIdentity", "PreferEOSIdentity", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferEOSIdentity; - flag_set = true; - } - else if (str_is_equal_to_any(flag_as_cstr, "EOS_IPMF_PreferIntegrated", "PreferIntegrated", "EOS_IPMF_PreferIntegratedIdentity", "PreferIntegratedIdentity", NULL)) - { - collected_flags |= EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferIntegratedIdentity; - flag_set = true; - } - } - - return flag_set ? collected_flags : EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_Disabled; -} - -//------------------------------------------------------------------------- -static EOSSteamConfig eos_steam_config_from_json_value(json_value_s *config_json) -{ - struct json_object_s* config_json_object = json_value_as_object(config_json); - struct json_object_element_s* iter = config_json_object->start; - EOSSteamConfig eos_config; - eos_config.flags; - - while (iter != nullptr) - { - if (!strcmp("flags", iter->name->string)) - { - eos_config.flags = eos_collect_integrated_platform_managment_flags(iter); - - } - else if (!strcmp("overrideLibraryPath", iter->name->string)) - { - const char *override_library_path = json_value_as_string(iter->value)->string; - - if (strcmp("NULL", override_library_path) - && strcmp("null", override_library_path) - ) - { - eos_config.OverrideLibraryPath = override_library_path; - } - - } - else if (!strcmp("steamSDKMajorVersion", iter->name->string)) - { - eos_config.steamSDKMajorVersion = json_value_as_uint32(iter->value); - } - else if (!strcmp("steamSDKMinorVersion", iter->name->string)) - { - eos_config.steamSDKMinorVersion = json_value_as_uint32(iter->value); - } - else if (!strcmp("steamApiInterfaceVersionsArray", iter->name->string)) - { - json_array_s* apiVersions = json_value_as_array(iter->value); - - for (auto e = apiVersions->start; e != nullptr; e = e->next) - { - eos_config.steamApiInterfaceVersionsArray.push_back(json_value_as_string(e->value)->string); - } - } - - iter = iter->next; - } - - return eos_config; -} - -//------------------------------------------------------------------------- -static std::filesystem::path get_path_for_eos_service_config(std::string config_filename) -{ - //return get_path_relative_to_current_module(std::filesystem::path("../..") / "StreamingAssets" / "EOS" / "EpicOnlineServicesConfig.json"); - auto twoDirsUp = std::filesystem::path("../.."); - std::filesystem::path packaged_data_path = get_path_relative_to_current_module(twoDirsUp); - std::error_code error_code; - - log_inform("about to look with exists"); - if (!std::filesystem::exists(packaged_data_path, error_code)) - { - log_warn("Didn't find the path twoDirsUp"); - packaged_data_path = get_path_relative_to_current_module(std::filesystem::path("./Data/")); - } - - return packaged_data_path / "StreamingAssets" / "EOS" / config_filename; -} - -//------------------------------------------------------------------------- -json_value_s* read_eos_config_as_json_value_from_file(std::string config_filename) -{ - std::filesystem::path path_to_config_json = get_path_for_eos_service_config(config_filename); - - return read_config_json_as_json_from_path(path_to_config_json); -} - -//------------------------------------------------------------------------- -static void EOS_Platform_Options_debug_log(const EOS_Platform_Options& platform_options) -{ - std::stringstream output; - output << platform_options.ApiVersion << "\n"; - output << platform_options.bIsServer << "\n"; - output << platform_options.Flags << "\n"; - output << platform_options.CacheDirectory << "\n"; - - output << platform_options.EncryptionKey << "\n"; - if (platform_options.OverrideCountryCode) - { - output << platform_options.OverrideCountryCode << "\n"; - } - - if (platform_options.OverrideLocaleCode) - { - output << platform_options.OverrideLocaleCode << "\n"; - } - output << platform_options.ProductId << "\n"; - output << platform_options.SandboxId << "\n"; - output << platform_options.DeploymentId << "\n"; - output << platform_options.ClientCredentials.ClientId << "\n"; - output << platform_options.ClientCredentials.ClientSecret << "\n"; - - auto *rtc_options = platform_options.RTCOptions; - auto *windows_rtc_options = (EOS_Windows_RTCOptions*)rtc_options->PlatformSpecificOptions; - - output << windows_rtc_options->ApiVersion << "\n"; - output << windows_rtc_options->XAudio29DllPath << "\n"; - - log_inform(output.str().c_str()); -} - -//------------------------------------------------------------------------- -static std::string basename(const std::string& path) -{ - std::string filename; - filename.resize(path.length() + 1); - _splitpath_s(path.c_str(), NULL, 0, NULL, 0, filename.data(), filename.size(), NULL, 0); - - return filename; -} - -//------------------------------------------------------------------------- -static void eos_call_steam_init(const std::filesystem::path& steam_dll_path) -{ - std::string steam_dll_path_as_string = steam_dll_path.string(); - eos_call_steam_init(steam_dll_path_as_string); -} - -//------------------------------------------------------------------------- -// This function assumes that if the caller has already loaded the steam DLL, -// that SteamAPI_Init doesn't need to be called -static void eos_call_steam_init(const std::string& steam_dll_path) -{ - auto steam_dll_path_string = basename(steam_dll_path); - HANDLE steam_dll_handle = GetModuleHandleA(steam_dll_path_string.c_str()); - - // Check the default name for the steam_api.dll - if (!steam_dll_handle) - { - steam_dll_handle = GetModuleHandleA("steam_api.dll"); - } - - // in the case that it's not loaded, try to load it from the user provided path - if (!steam_dll_handle) - { - steam_dll_handle = load_library_at_path(steam_dll_path); - } - - if (steam_dll_handle != nullptr) - { - typedef bool(__cdecl* SteamAPI_Init_t)(); - SteamAPI_Init_t SteamAPI_Init = load_function_with_name(steam_dll_handle, "SteamAPI_Init"); - - if (SteamAPI_Init()) - { - log_inform("Called SteamAPI_Init with success!"); - } - } -} - -//------------------------------------------------------------------------- -void eos_set_loglevel_via_config() -{ - if (EOS_Logging_SetLogLevel_ptr == nullptr) - { - return; - } - - auto path_to_log_config_json = get_path_for_eos_service_config(EOS_LOGLEVEL_CONFIG_FILENAME); - - if (!std::filesystem::exists(path_to_log_config_json)) - { - log_inform("Log level config not found, using default log levels"); - return; - } - - json_value_s* log_config_as_json = read_config_json_as_json_from_path(path_to_log_config_json); - LogLevelConfig log_config = log_config_from_json_value(log_config_as_json); - free(log_config_as_json); - - // Validation to prevent out of range exception - if (log_config.category.size() != log_config.level.size()) - { - log_warn("Log level config entries out of range"); - return; - } - - // Last in the vector is AllCategories, and will not be set - size_t individual_category_size = log_config.category.size() > 0 ? log_config.category.size() - 1 : 0; - if (individual_category_size == 0) - { - log_warn("Log level config entries empty"); - return; - } - - for (size_t i = 0; i < individual_category_size; i++) - { - EOS_Logging_SetLogLevel_ptr((EOS_ELogCategory)i, eos_loglevel_str_to_enum(log_config.level[i])); - } - - log_inform("Log levels set according to config"); -} - -//------------------------------------------------------------------------- -void eos_create(EOSConfig& eosConfig) -{ - EOS_Platform_Options platform_options = {0}; - platform_options.ApiVersion = EOS_PLATFORM_OPTIONS_API_LATEST; - platform_options.bIsServer = eosConfig.isServer; - platform_options.Flags = eosConfig.flags; - platform_options.CacheDirectory = GetCacheDirectory(); - - platform_options.EncryptionKey = eosConfig.encryptionKey.length() > 0 ? eosConfig.encryptionKey.c_str() : nullptr; - platform_options.OverrideCountryCode = eosConfig.overrideCountryCode.length() > 0 ? eosConfig.overrideCountryCode.c_str() : nullptr; - platform_options.OverrideLocaleCode = eosConfig.overrideLocaleCode.length() > 0 ? eosConfig.overrideLocaleCode.c_str() : nullptr; - platform_options.ProductId = eosConfig.productID.c_str(); - platform_options.SandboxId = eosConfig.sandboxID.c_str(); - platform_options.DeploymentId = eosConfig.deploymentID.c_str(); - platform_options.ClientCredentials.ClientId = eosConfig.clientID.c_str(); - platform_options.ClientCredentials.ClientSecret = eosConfig.clientSecret.c_str(); - - platform_options.TickBudgetInMilliseconds = eosConfig.tickBudgetInMilliseconds; - - if (eosConfig.taskNetworkTimeoutSeconds > 0) - { - platform_options.TaskNetworkTimeoutSeconds = &eosConfig.taskNetworkTimeoutSeconds; - } - - EOS_Platform_RTCOptions rtc_options = { 0 }; - - rtc_options.ApiVersion = EOS_PLATFORM_RTCOPTIONS_API_LATEST; -#if PLATFORM_WINDOWS - log_inform("setting up rtc"); - fs::path xaudio2_dll_path = get_path_relative_to_current_module(XAUDIO2_DLL_NAME); - std::string xaudio2_dll_path_as_string = to_utf8_str(xaudio2_dll_path); - EOS_Windows_RTCOptions windows_rtc_options = { 0 }; - windows_rtc_options.ApiVersion = EOS_WINDOWS_RTCOPTIONS_API_LATEST; - windows_rtc_options.XAudio29DllPath = xaudio2_dll_path_as_string.c_str(); - log_warn(xaudio2_dll_path_as_string.c_str()); - - if (!fs::exists(xaudio2_dll_path)) - { - log_warn("Missing XAudio dll!"); - } - rtc_options.PlatformSpecificOptions = &windows_rtc_options; - platform_options.RTCOptions = &rtc_options; -#endif - -#if PLATFORM_WINDOWS - auto path_to_steam_config_json = get_path_for_eos_service_config(EOS_STEAM_CONFIG_FILENAME); - - // Defined here so that the override path lives long enough to be referenced by the create option - EOSSteamConfig eos_steam_config; - EOS_IntegratedPlatform_Options steam_integrated_platform_option = { 0 }; - EOS_IntegratedPlatform_Steam_Options steam_platform = { 0 }; - EOS_HIntegratedPlatformOptionsContainer integrated_platform_options_container = nullptr; - std::wstring_convert> converter; - - if (std::filesystem::exists(path_to_steam_config_json)) - { - json_value_s* eos_steam_config_as_json = nullptr; - eos_steam_config_as_json = read_config_json_as_json_from_path(path_to_steam_config_json); - eos_steam_config = eos_steam_config_from_json_value(eos_steam_config_as_json); - free(eos_steam_config_as_json); - - if (eos_steam_config.OverrideLibraryPath.has_value()) - { - if (!std::filesystem::exists(eos_steam_config.OverrideLibraryPath.value())) - { - auto override_lib_path_as_str = basename(eos_steam_config.OverrideLibraryPath.value()); - auto found_steam_path = get_path_relative_to_current_module(override_lib_path_as_str); - - // Fall back and use the steam dll name based on the - // type of binary the GfxPluginNativeRender - if (!std::filesystem::exists(found_steam_path) || eos_steam_config.OverrideLibraryPath.value().empty()) - { - found_steam_path = get_path_relative_to_current_module(STEAM_DLL_NAME); - } - - if (std::filesystem::exists(found_steam_path)) - { - eos_steam_config.OverrideLibraryPath = converter.to_bytes(found_steam_path.wstring()); - } - } - } - else - { - auto found_steam_path = get_path_relative_to_current_module(STEAM_DLL_NAME); - if (std::filesystem::exists(found_steam_path)) - { - eos_steam_config.OverrideLibraryPath = converter.to_bytes(found_steam_path.wstring()); - } - } - - if (eos_steam_config.isManagedByApplication()) - { - eos_call_steam_init(eos_steam_config.OverrideLibraryPath.value()); - eos_steam_config.OverrideLibraryPath.reset(); - } - - if (eos_steam_config.OverrideLibraryPath.has_value()) - { - steam_platform.OverrideLibraryPath = eos_steam_config.OverrideLibraryPath.value().c_str(); - } - - steam_platform.SteamMajorVersion = eos_steam_config.steamSDKMajorVersion; - steam_platform.SteamMinorVersion = eos_steam_config.steamSDKMinorVersion; - - // For each element in the array (each of which is a string of an api version information) - // iterate across each character, and at the end of a string add a null terminator \0 - // then add one more null terminator at the end of the array - std::vector steamApiInterfaceVersionsAsCharArray; - - for (const auto& currentFullValue : eos_steam_config.steamApiInterfaceVersionsArray) - { - for (char currentCharacter : currentFullValue) - { - steamApiInterfaceVersionsAsCharArray.push_back(currentCharacter); - } - - steamApiInterfaceVersionsAsCharArray.push_back('\0'); - } - steamApiInterfaceVersionsAsCharArray.push_back('\0'); - - steam_platform.SteamApiInterfaceVersionsArray = reinterpret_cast(steamApiInterfaceVersionsAsCharArray.data()); - - auto size = steamApiInterfaceVersionsAsCharArray.size(); - - if (size > EOS_INTEGRATEDPLATFORM_STEAM_MAX_STEAMAPIINTERFACEVERSIONSARRAY_SIZE) - { - log_error("Size given for SteamApiInterfaceVersionsAsCharArray exceeds the maximum value."); - } - else - { - // steam_platform needs to have a count of how many bytes the "array" is, stored in SteamApiInterfaceVersionsArrayBytes - // This has some fuzzy behavior; if you set it to 0 or count it up properly, there won't be a logged problem - // if you put a non-zero amount that is insufficient, there will be an unclear logged error message - steam_platform.SteamApiInterfaceVersionsArrayBytes = static_cast(size); - } - - steam_integrated_platform_option.ApiVersion = EOS_INTEGRATEDPLATFORM_OPTIONS_API_LATEST; - steam_integrated_platform_option.Type = EOS_IPT_Steam; - steam_integrated_platform_option.Flags = eos_steam_config.flags; - steam_integrated_platform_option.InitOptions = &steam_platform; - - steam_platform.ApiVersion = EOS_INTEGRATEDPLATFORM_STEAM_OPTIONS_API_LATEST; - - EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainerOptions options = { EOS_INTEGRATEDPLATFORM_CREATEINTEGRATEDPLATFORMOPTIONSCONTAINER_API_LATEST }; - EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr(&options, &integrated_platform_options_container); - platform_options.IntegratedPlatformOptionsContainerHandle = integrated_platform_options_container; - - EOS_IntegratedPlatformOptionsContainer_AddOptions addOptions = { EOS_INTEGRATEDPLATFORMOPTIONSCONTAINER_ADD_API_LATEST }; - addOptions.Options = &steam_integrated_platform_option; - EOS_IntegratedPlatformOptionsContainer_Add_ptr(integrated_platform_options_container, &addOptions); - } -#endif - - //EOS_Platform_Options_debug_log(platform_options); - log_inform("run EOS_Platform_Create"); - eos_platform_handle = EOS_Platform_Create_ptr(&platform_options); - if (integrated_platform_options_container) - { - EOS_IntegratedPlatformOptionsContainer_Release_ptr(integrated_platform_options_container); - } - - if (!eos_platform_handle) - { - log_error("failed to create the platform"); - } -} - -//------------------------------------------------------------------------- -static bool QueryRegKey(const HKEY InKey, const TCHAR* InSubKey, const TCHAR* InValueName, std::wstring& OutData) -{ - bool bSuccess = false; -#if PLATFORM_WINDOWS - // Redirect key depending on system - for (uint32_t RegistryIndex = 0; RegistryIndex < 2 && !bSuccess; ++RegistryIndex) - { - HKEY Key = 0; - const uint32_t RegFlags = (RegistryIndex == 0) ? KEY_WOW64_32KEY : KEY_WOW64_64KEY; - if (RegOpenKeyEx(InKey, InSubKey, 0, KEY_READ | RegFlags, &Key) == ERROR_SUCCESS) - { - ::DWORD Size = 0; - // First, we'll call RegQueryValueEx to find out how large of a buffer we need - if ((RegQueryValueEx(Key, InValueName, NULL, NULL, NULL, &Size) == ERROR_SUCCESS) && Size) - { - // Allocate a buffer to hold the value and call the function again to get the data - char *Buffer = new char[Size]; - if (RegQueryValueEx(Key, InValueName, NULL, NULL, (LPBYTE)Buffer, &Size) == ERROR_SUCCESS) - { - const uint32_t Length = (Size / sizeof(TCHAR)) - 1; - OutData = (TCHAR*)Buffer; - bSuccess = true; - } - delete[] Buffer; - } - RegCloseKey(Key); - } - } -#endif - return bSuccess; -} - -//------------------------------------------------------------------------- -// Currently this only works on windows -static bool get_overlay_dll_path(fs::path* OutDllPath) -{ -#if PLATFORM_WINDOWS - const TCHAR* RegKey = TEXT(R"(SOFTWARE\Epic Games\EOS)"); - const TCHAR* RegValue = TEXT("OverlayPath"); - std::wstring OverlayDllDirectory; - - if (!QueryRegKey(HKEY_CURRENT_USER, RegKey, RegValue, OverlayDllDirectory)) - { - if (!QueryRegKey(HKEY_LOCAL_MACHINE, RegKey, RegValue, OverlayDllDirectory)) - { - return false; - } - } - - *OutDllPath = fs::path(OverlayDllDirectory) / OVERLAY_DLL_NAME; - return fs::exists(*OutDllPath) && fs::is_regular_file(*OutDllPath); -#else - log_inform("Trying to get a DLL path on a platform without DLL paths searching"); - return false; -#endif -} - -//------------------------------------------------------------------------- -static void FetchEOSFunctionPointers() -{ - // The '@' in the function names is apart of how names are mangled on windows. The value after the '@' is the size of the params on the stack - EOS_Initialize_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Initialize@4", "EOS_Initialize")); - EOS_Shutdown_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Shutdown@0", "EOS_Shutdown")); - EOS_Platform_Create_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Platform_Create@4", "EOS_Platform_Create")); - EOS_Platform_Release_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Platform_Release@4", "EOS_Platform_Release")); - EOS_Logging_SetLogLevel_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Logging_SetLogLevel@8", "EOS_Logging_SetLogLevel")); - EOS_Logging_SetCallback_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("EOS_Logging_SetCallback@4", "EOS_Logging_SetCallback")); - - EOS_IntegratedPlatformOptionsContainer_Add_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_IntegratedPlatformOptionsContainer_Add@8", "EOS_IntegratedPlatformOptionsContainer_Add")); - EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer@8", "EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer")); - EOS_IntegratedPlatformOptionsContainer_Release_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_IntegratedPlatformOptionsContainer_Release@4", "EOS_IntegratedPlatformOptionsContainer_Release")); -} - -//------------------------------------------------------------------------- -// Called by unity on load. It kicks off the work to load the DLL for Overlay -#if PLATFORM_32BITS -#pragma comment(linker, "/export:UnityPluginLoad=_UnityPluginLoad@4") -#endif -DLL_EXPORT(void) UnityPluginLoad(void*) -{ -#if _DEBUG - show_log_as_dialog("You may attach a debugger to the DLL"); -#endif - - auto path_to_config_json = get_path_for_eos_service_config(EOS_SERVICE_CONFIG_FILENAME); - json_value_s* eos_config_as_json = nullptr; - - eos_config_as_json = read_config_json_from_dll(); - - if (!eos_config_as_json && std::filesystem::exists(path_to_config_json)) - { - eos_config_as_json = read_config_json_as_json_from_path(path_to_config_json); - } - - if (!eos_config_as_json) - { - log_warn("Failed to load a valid json config for EOS"); - return; - } - - EOSConfig eos_config = eos_config_from_json_value(eos_config_as_json); - free(eos_config_as_json); - -#if PLATFORM_WINDOWS - //support sandbox and deployment id override via command line arguments - std::stringstream argStream = std::stringstream(GetCommandLineA()); - std::istream_iterator argsBegin(argStream); - std::istream_iterator argsEnd; - std::vector argStrings(argsBegin, argsEnd); - std::string egsArgName = "-epicsandboxid="; - std::string sandboxArgName = "-eossandboxid="; - for (unsigned i = 0; i < argStrings.size(); ++i) - { - std::string* match = nullptr; - if (argStrings[i]._Starts_with(sandboxArgName)) - { - match = &sandboxArgName; - } - else if(argStrings[i]._Starts_with(egsArgName)) - { - match = &egsArgName; - } - if (match != nullptr) - { - std::string sandboxArg = argStrings[i].substr(match->length()); - if (!sandboxArg.empty()) - { - log_inform(("Sandbox ID override specified: " + sandboxArg).c_str()); - eos_config.sandboxID = sandboxArg; - } - } - } -#endif - - //check if a deployment id override exists for sandbox id - for (unsigned i = 0; i < eos_config.sandboxDeploymentOverrides.size(); ++i) - { - if (eos_config.sandboxID == eos_config.sandboxDeploymentOverrides[i].sandboxID) - { - log_inform(("Sandbox Deployment ID override specified: " + eos_config.sandboxDeploymentOverrides[i].deploymentID).c_str()); - eos_config.deploymentID = eos_config.sandboxDeploymentOverrides[i].deploymentID; - } - } - -#if PLATFORM_WINDOWS - std::string deploymentArgName = "-eosdeploymentid="; - std::string egsDeploymentArgName = "-epicdeploymentid="; - for (unsigned i = 0; i < argStrings.size(); ++i) - { - std::string* match = nullptr; - if (argStrings[i]._Starts_with(deploymentArgName)) - { - match = &deploymentArgName; - } - else if (argStrings[i]._Starts_with(egsDeploymentArgName)) - { - match = &egsDeploymentArgName; - } - if (match != nullptr) - { - std::string deploymentArg = argStrings[i].substr(match->length()); - if (!deploymentArg.empty()) - { - log_inform(("Deployment ID override specified: " + deploymentArg).c_str()); - eos_config.deploymentID = deploymentArg; - } - } - } -#endif - -#if _DEBUG - global_log_open("gfx_log.txt"); -#endif - - fs::path DllPath; - log_inform("On UnityPluginLoad"); - //if (!get_overlay_dll_path(&DllPath)) - //{ - // show_log_as_dialog("Missing Overlay DLL!\n Overlay functionality will not work!"); - //} - - s_eos_sdk_lib_handle = load_library_at_path(get_path_relative_to_current_module(SDK_DLL_NAME)); - - //eos_sdk_overlay_lib_handle = load_library_at_path(DllPath); - //if (eos_sdk_overlay_lib_handle) - //{ - // log_warn("loaded eos overlay sdk"); - // FuncApplicationWillShutdown = load_function_with_name(eos_sdk_overlay_lib_handle, "EOS_Overlay_Initilize"); - // if(FuncApplicationWillShutdown == nullptr) - // { - // log_warn("Unable to find overlay function"); - // } - //} - - if (s_eos_sdk_lib_handle) - { - FetchEOSFunctionPointers(); - - if (EOS_Initialize_ptr) - { - log_inform("start eos init"); - - eos_init(eos_config); - - eos_set_loglevel_via_config(); - //log_warn("start eos create"); - eos_create(eos_config); - - // This code is commented out because the handle is now handed off to the C# code - //EOS_Platform_Release(eos_platform_handle); - //eos_platform_handle = NULL; - //log_warn("start eos shutdown"); - //EOS_Shutdown(); - //log_warn("unload eos sdk"); - //unload_library(s_eos_sdk_lib_handle); - - s_eos_sdk_lib_handle = NULL; - EOS_Initialize_ptr = NULL; - EOS_Shutdown_ptr = NULL; - EOS_Platform_Create_ptr = NULL; - } - else - { - log_warn("unable to find EOS_Initialize"); - } - - } - else - { - log_warn("Couldn't find dll " SDK_DLL_NAME); - } - -} - -//------------------------------------------------------------------------- -#if PLATFORM_32BITS -#pragma comment(linker, "/export:_UnityPluginUnload=_UnityPluginUnload@0") -#endif -DLL_EXPORT(void) UnityPluginUnload() -{ - if (FuncApplicationWillShutdown != nullptr) - { - FuncApplicationWillShutdown(); - } - unload_library(s_eos_sdk_overlay_lib_handle); - s_eos_sdk_overlay_lib_handle = nullptr; - - global_log_close(); -} - -//------------------------------------------------------------------------- -DLL_EXPORT(void *) EOS_GetPlatformInterface() -{ - return eos_platform_handle; -} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/framework.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/framework.h deleted file mode 100644 index 54b83e94f..000000000 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/framework.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -// Windows Header Files -#include diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/PEW_EOS_Defines.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/PEW_EOS_Defines.h new file mode 100644 index 000000000..b3e64932c --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/PEW_EOS_Defines.h @@ -0,0 +1,57 @@ +#ifndef PEW_EOS_DEFINES_H +#define PEW_EOS_DEFINES_H + +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +// Define PEW_EOS_EXPORT for exporting/importing symbols +#ifdef _WIN32 +// PEW_EOS_EXPORT is defined *only* in the pre-processing scripting directives +// for GfxPluginNativeRender's build process. When PEW_EOS_EXPORT is defined, +// using the PEW_EOS_API macro in class or struct definitions will affect the +// export of those objects. However, when other projects reference any of the +// files within this project - the PEW_EOS_EXPORT define will not be defined, +// and (as is appropriate for the context in which another project is +// referencing this one) the macro will specify that the struct or class should +// be *imported*, not *exported*. +// This is standard practice for C++ DLL projects. +#ifdef PEW_EOS_EXPORT +// Specify export +#define PEW_EOS_API __declspec(dllexport) +#else +// Specify import +#define PEW_EOS_API __declspec(dllimport) +#endif +#else +#define PEW_EOS_API // Empty for non-Windows platforms +#endif + +/** + * \brief Use this macro on the signature of a method that is to be exposed to + * callers external to the dll. Used exclusively for static methods. + * \param return_value Syntax to affect the exposure of the method to callers + * external to the DLL. + */ +#define PEW_EOS_API_FUNC(return_value) extern "C" PEW_EOS_API return_value __stdcall + +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/config_legacy.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/config_legacy.h new file mode 100644 index 000000000..ef4a9f609 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/config_legacy.h @@ -0,0 +1,281 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include + +#include "json.h" +#include "eos_sdk.h" + +struct json_value_s; + +#define CONFIG_EXPORTS + +#ifdef CONFIG_EXPORTS +#define CONFIG_API __declspec(dllexport) +#else +#define CONFIG_API __declspec(dllimport) +#endif + +namespace pew::eos::config_legacy +{ + /** + * \brief + * Maps string values to values defined by the EOS SDK regarding platform + * creation. + */ + static const std::map PLATFORM_CREATION_FLAGS_STRINGS_TO_ENUM = { + {"EOS_PF_LOADING_IN_EDITOR", EOS_PF_LOADING_IN_EDITOR}, + {"LoadingInEditor", EOS_PF_LOADING_IN_EDITOR}, + + {"EOS_PF_DISABLE_OVERLAY", EOS_PF_DISABLE_OVERLAY}, + {"DisableOverlay", EOS_PF_DISABLE_OVERLAY}, + + {"EOS_PF_DISABLE_SOCIAL_OVERLAY", EOS_PF_DISABLE_SOCIAL_OVERLAY}, + {"DisableSocialOverlay", EOS_PF_DISABLE_SOCIAL_OVERLAY}, + + {"EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D9", EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D9}, + {"WindowsEnableOverlayD3D9", EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D9}, + + {"EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D10", EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D10}, + {"WindowsEnableOverlayD3D10", EOS_PF_WINDOWS_ENABLE_OVERLAY_D3D10}, + + {"EOS_PF_WINDOWS_ENABLE_OVERLAY_OPENGL", EOS_PF_WINDOWS_ENABLE_OVERLAY_OPENGL}, + {"WindowsEnableOverlayOpengl", EOS_PF_WINDOWS_ENABLE_OVERLAY_OPENGL}, + + {"EOS_PF_CONSOLE_ENABLE_OVERLAY_AUTOMATIC_UNLOADING", EOS_PF_CONSOLE_ENABLE_OVERLAY_AUTOMATIC_UNLOADING}, + {"ConsoleEnableOverlayAutomaticUnloading", EOS_PF_CONSOLE_ENABLE_OVERLAY_AUTOMATIC_UNLOADING}, + + {"EOS_PF_RESERVED1", EOS_PF_RESERVED1}, + {"Reserved1", EOS_PF_RESERVED1} + }; + + /** + * \brief Maps string values to values within the + * EOS_EIntegratedPlatformManagementFlags enum. + */ + static const std::map INTEGRATED_PLATFORM_MANAGEMENT_FLAGS_STRINGS_TO_ENUM = { + {"EOS_IPMF_Disabled", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_Disabled }, + {"Disabled", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_Disabled }, + + {"EOS_IPMF_LibraryManagedByApplication", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication }, + {"EOS_IPMF_ManagedByApplication", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication}, + {"ManagedByApplication", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication}, + {"LibraryManagedByApplication", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication}, + + {"ManagedBySDK", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK }, + {"EOS_IPMF_ManagedBySDK", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK }, + {"EOS_IPMF_LibraryManagedBySDK", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK }, + {"LibraryManagedBySDK", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK }, + + {"DisableSharedPresence", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisablePresenceMirroring }, + {"EOS_IPMF_DisableSharedPresence", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisablePresenceMirroring }, + {"EOS_IPMF_DisablePresenceMirroring", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisablePresenceMirroring }, + {"DisablePresenceMirroring", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisablePresenceMirroring}, + + {"DisableSessions", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisableSDKManagedSessions }, + {"EOS_IPMF_DisableSessions", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisableSDKManagedSessions }, + {"EOS_IPMF_DisableSDKManagedSessions", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisableSDKManagedSessions }, + {"DisableSDKManagedSessions", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_DisableSDKManagedSessions }, + + {"PreferEOS", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferEOSIdentity }, + {"EOS_IPMF_PreferEOS", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferEOSIdentity }, + {"EOS_IPMF_PreferEOSIdentity", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferEOSIdentity }, + {"PreferEOSIdentity", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferEOSIdentity}, + + {"PreferIntegrated", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferIntegratedIdentity }, + {"EOS_IPMF_PreferIntegrated", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferIntegratedIdentity }, + {"EOS_IPMF_PreferIntegratedIdentity", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferIntegratedIdentity }, + {"PreferIntegratedIdentity", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_PreferIntegratedIdentity}, + + {"EOS_IPMF_ApplicationManagedIdentityLogin", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_ApplicationManagedIdentityLogin }, + {"ApplicationManagedIdentityLogin", EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_ApplicationManagedIdentityLogin} + }; + + /** + * @brief Typedef for a function pointer that retrieves the configuration as a JSON string. + * + * This function pointer type represents a function that, when called, + * returns the configuration data as a JSON-formatted string. + */ + typedef const char* (*GetConfigAsJSONString_t)(); + + /** + * @brief Represents a sandbox deployment override configuration. + * + * Contains the sandbox ID and the corresponding deployment ID to override the default sandbox + * deployment configuration. + */ + struct SandboxDeploymentOverride + { + std::string sandboxID; + std::string deploymentID; + }; + + + /** + * @brief Holds EOS platform configuration settings. + * + * This structure defines various configuration parameters for the EOS platform, including + * product details, sandbox information, client credentials, thread affinities, and platform + * options. + */ + struct EOSConfig + { + std::string productName; + std::string productVersion; + + std::string productID; + std::string sandboxID; + std::string deploymentID; + std::vector sandboxDeploymentOverrides; + + std::string clientSecret; + std::string clientID; + std::string encryptionKey; + + std::string overrideCountryCode; + std::string overrideLocaleCode; + + // this is called platformOptionsFlags in C# + uint64_t flags = 0; + + uint32_t tickBudgetInMilliseconds = 0; + double taskNetworkTimeoutSeconds = 0.0; + + uint64_t ThreadAffinity_networkWork = 0; + uint64_t ThreadAffinity_storageIO = 0; + uint64_t ThreadAffinity_webSocketIO = 0; + uint64_t ThreadAffinity_P2PIO = 0; + uint64_t ThreadAffinity_HTTPRequestIO = 0; + uint64_t ThreadAffinity_RTCIO = 0; + + bool isServer = false; + + }; + + /** + * @brief Holds configuration for log levels and categories. + * + * This structure defines log level settings for specific log categories. + */ + struct LogLevelConfig + { + std::vector category; + std::vector level; + }; + + /** + * @brief Configuration settings specific to the EOS Steam platform integration. + * + * This structure defines settings for integrating with the Steam platform, including SDK versions, + * platform flags, and API versions. + */ + struct EOSSteamConfig + { + EOS_EIntegratedPlatformManagementFlags flags = static_cast(0); + uint32_t steamSDKMajorVersion = 0; + uint32_t steamSDKMinorVersion = 0; + std::optional OverrideLibraryPath; + std::vector steamApiInterfaceVersionsArray; + + /** + * @brief Checks if the library is managed by the application. + * + * @return `true` if the library is managed by the application, `false` otherwise. + */ + bool is_managed_by_application() const; + + /** + * @brief Checks if the library is managed by the EOS SDK. + * + * @return `true` if the library is managed by the SDK, `false` otherwise. + */ + bool is_managed_by_sdk() const; + }; + + /** + * @brief Parses a JSON configuration object to create a log level configuration. + * + * Extracts log category and level pairs from a JSON structure, storing them in a `LogLevelConfig` object. + * + * @param config_json The JSON value representing the configuration. + * @return A `LogLevelConfig` object populated with log categories and their levels. + */ + LogLevelConfig log_config_from_json_value(json_value_s* config_json); + + /** + * @brief Retrieves the path to the EOS service configuration file. + * + * Attempts to locate the configuration file by navigating up directories. + * If the path is not found, it defaults to a predefined directory. + * + * @param config_filename The name of the configuration file. + * @return The path to the EOS service configuration file. + */ + std::filesystem::path get_path_for_eos_service_config(std::string config_filename); + + /** + * @brief Reads a JSON configuration from a DLL. + * + * Attempts to load configuration data from a DLL. The function retrieves the + * JSON configuration string from the DLL, parses it, and returns the parsed JSON object. + * + * @return A pointer to a `json_value_s` representing the configuration data, or `nullptr` if loading fails. + */ + json_value_s* read_config_json_from_dll(); + + /** + * \brief Parses an EOS configuration object from a JSON structure. + * \return An `EOSConfig` object populated with settings from the JSON structure. + */ + CONFIG_API bool try_get_eos_config(EOSConfig& config); + + /** + * @brief Collects integrated platform management flags from a JSON element. + * + * Parses platform management flags from a JSON element and combines them into a single flag value. + * + * @param iter The JSON object element representing the flags. + * @return The combined `EOS_EIntegratedPlatformManagementFlags` value. + */ + EOS_EIntegratedPlatformManagementFlags eos_collect_integrated_platform_management_flags(json_object_element_s* iter); + + /** + * @brief Parses an EOS Steam configuration from a JSON structure. + * + * Reads Steam-specific settings, such as flags, library path, SDK versions, and API versions, + * from the provided JSON configuration, and stores them in an `EOSSteamConfig` object. + * + * @param config_json The JSON value representing the Steam configuration. + * @return An `EOSSteamConfig` object populated with settings from the JSON structure. + */ + EOSSteamConfig eos_steam_config_from_json_value(json_value_s* config_json); +} +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_helpers.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_helpers.h new file mode 100644 index 000000000..df01d11be --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_helpers.h @@ -0,0 +1,91 @@ +#ifndef EOS_HELPERS_H +#define EOS_HELPERS_H +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once +#include "PEW_EOS_Defines.h" + +namespace std +{ + namespace filesystem + { + class path; + } +} + +namespace pew::eos +{ + namespace config_legacy + { + struct EOSConfig; + } + + /** + * @brief Retrieves the EOS platform interface handle. + * + * Provides access to the EOS platform interface handle managed by the library. + * + * @return A pointer to the EOS platform interface handle. + */ + PEW_EOS_API_FUNC(void*) EOS_GetPlatformInterface(); + + /** + * @brief Sets the log level for the EOS SDK based on the configuration file. + * + * Reads the log level configuration file and applies specified log levels to each + * log category in the EOS SDK. If the configuration file is missing or the entries are + * invalid, default log levels are used. + */ + void eos_set_loglevel_via_config(); + + /** + * @brief Logs EOS platform options for debugging purposes. + * + * Logs various properties of the EOS platform options structure, including version, + * server mode, flags, cache directory, and client credentials, for debugging. + * + * @param platform_options The EOS platform options structure to log. + */ + void EOS_Platform_Options_debug_log(const EOS_Platform_Options& platform_options); + + /** + * @brief Initializes the EOS SDK with the provided configuration. + * + * Sets up and initializes the EOS SDK using the provided configuration. Sets log levels and + * a logging callback if configured. If initialization fails, an error is logged. + * + * @param eos_config The EOS configuration settings. + */ + void eos_init(const config_legacy::EOSConfig eos_config); + + /** + * @brief Creates an EOS platform using the specified configuration. + * + * Configures and creates an EOS platform instance. This includes setting up RTC options, + * integrated platform options, and other settings defined in the configuration. + * + * @param eos_config The configuration object containing EOS platform settings. + */ + void eos_create(config_legacy::EOSConfig eos_config); +} +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_library_helpers.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_library_helpers.h new file mode 100644 index 000000000..8fc07ae9f --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_library_helpers.h @@ -0,0 +1,142 @@ +#ifndef EOS_LIBRARY_HELPERS_H +#define EOS_LIBRARY_HELPERS_H +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include + +/** + * \brief Forward declarations + */ +enum class EOS_ELogLevel; +enum class EOS_ELogCategory; + +namespace pew::eos::eos_library_helpers +{ + typedef EOS_EResult(EOS_CALL* EOS_Initialize_t)(const EOS_InitializeOptions* Options); + typedef EOS_EResult(EOS_CALL* EOS_Shutdown_t)(); + typedef EOS_HPlatform(EOS_CALL* EOS_Platform_Create_t)(const EOS_Platform_Options* Options); + typedef EOS_EResult(EOS_CALL* EOS_Logging_SetCallback_t)(EOS_LogMessageFunc Callback); + typedef EOS_EResult(EOS_CALL* EOS_Logging_SetLogLevel_t)(EOS_ELogCategory LogCategory, EOS_ELogLevel LogLevel); + typedef EOS_EResult(*EOS_IntegratedPlatformOptionsContainer_Add_t)(EOS_HIntegratedPlatformOptionsContainer Handle, const EOS_IntegratedPlatformOptionsContainer_AddOptions* InOptions); + typedef EOS_EResult(*EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_t)(const EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainerOptions* Options, EOS_HIntegratedPlatformOptionsContainer* OutIntegratedPlatformOptionsContainerHandle); + typedef void (*EOS_IntegratedPlatformOptionsContainer_Release_t)(EOS_HIntegratedPlatformOptionsContainer IntegratedPlatformOptionsContainerHandle); + + extern EOS_Initialize_t EOS_Initialize_ptr; + extern EOS_Shutdown_t EOS_Shutdown_ptr; + extern EOS_Platform_Create_t EOS_Platform_Create_ptr; + extern EOS_Logging_SetCallback_t EOS_Logging_SetCallback_ptr; + extern EOS_Logging_SetLogLevel_t EOS_Logging_SetLogLevel_ptr; + extern EOS_IntegratedPlatformOptionsContainer_Add_t EOS_IntegratedPlatformOptionsContainer_Add_ptr; + extern EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_t EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr; + extern EOS_IntegratedPlatformOptionsContainer_Release_t EOS_IntegratedPlatformOptionsContainer_Release_ptr; + + extern void* s_eos_sdk_lib_handle; + extern void* s_eos_sdk_overlay_lib_handle; + + extern EOS_HPlatform eos_platform_handle; + + /** + * @brief Loads a dynamic library from the specified file path. + * + * Attempts to load the library at the specified path and returns a handle to it. + * On Windows, it uses `LoadLibrary` to perform the loading. + * + * @param library_path The file path to the library to load. + * @return A handle to the loaded library, or `nullptr` if loading fails. + */ + void* load_library_at_path(const std::filesystem::path& library_path); + + /** + * @brief Retrieves a function pointer by name from a loaded library. + * + * Uses the provided library handle to obtain the address of a specified function. + * On Windows, it uses `GetProcAddress` to retrieve the function pointer. + * + * @param library_handle A handle to the loaded library. + * @param function The name of the function to retrieve. + * @return A pointer to the specified function, or `nullptr` if not found. + */ + void* load_function_with_name(void* library_handle, const char* function); + + /** + * @brief Retrieves a function pointer of a specified type from a loaded library. + * + * This templated function casts the retrieved function pointer to the specified type. + * + * @tparam T The type of the function pointer. + * @param library_handle A handle to the loaded library. + * @param function The name of the function to retrieve. + * @return A pointer to the specified function cast to the specified type. + */ + template + T load_function_with_name(void* library_handle, const char* function) + { + return reinterpret_cast(load_function_with_name(library_handle, function)); + } + + /** + * @brief Loads EOS SDK function pointers from the loaded EOS SDK library. + * + * Maps specific function names from the loaded EOS SDK library to internal pointers, allowing + * the library to call various EOS SDK functions. + */ + void FetchEOSFunctionPointers(); + + /** + * @brief Queries a registry key for a specific value on Windows. + * + * Attempts to read a value from the specified registry key and subkey. It supports both + * 32-bit and 64-bit registry views. + * + * @param InKey The registry key handle. + * @param InSubKey The name of the subkey to query. + * @param InValueName The name of the value to retrieve. + * @param OutData The output parameter to store the retrieved data. + * @return `true` if the value was successfully retrieved, `false` otherwise. + */ + bool QueryRegKey(const HKEY InKey, const TCHAR* InSubKey, const TCHAR* InValueName, std::wstring& OutData); + + /** + * @brief Unloads a previously loaded dynamic library. + * + * Frees the handle to a loaded library, releasing associated resources. + * On Windows, it uses `FreeLibrary` to unload the library. + * + * @param library_handle The handle to the library to unload. + */ + void unload_library(void* library_handle); + + /** + * @brief Retrieves the path to the overlay DLL. + * + * Attempts to retrieve the overlay DLL path from the system registry on Windows. + * + * @param[out] OutDllPath The output parameter where the overlay DLL path is stored. + * @return `true` if the DLL path was found and exists; otherwise, `false`. + */ + static bool get_overlay_dll_path(std::filesystem::path* OutDllPath); +} +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/eos_minimum_includes.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_minimum_includes.h similarity index 100% rename from lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/eos_minimum_includes.h rename to lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/eos_minimum_includes.h diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/io_helpers.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/io_helpers.h new file mode 100644 index 000000000..20242f0e9 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/io_helpers.h @@ -0,0 +1,86 @@ +#ifndef IO_HELPERS_H +#define IO_HELPERS_H +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + + /** + * @file io_helpers.h + * @brief Helper functions for file I/O and path operations. + * + * This file provides utility functions for managing file paths, including + * obtaining the path to the current module and resolving relative paths. + */ + +namespace pew::eos::io_helpers +{ + /** + * @brief Retrieves the file path of a specified module as a dynamically allocated string. + * + * This function attempts to get the full file path of the module specified by the `module` handle. + * If the buffer size is insufficient, it reallocates with a larger size and retries until the path + * is successfully retrieved. The caller is responsible for freeing the returned string to avoid memory leaks. + * + * @param module The handle to the module whose path is to be retrieved. + * @return A dynamically allocated `TCHAR*` containing the path to the specified module, or `nullptr` if memory allocation fails. + * + * @note The caller must free the returned `TCHAR*` using `free()` to prevent memory leaks. + * @warning If the `module` handle is invalid, the function may fail or produce undefined behavior. + */ + TCHAR* get_path_to_module(HMODULE module); + + /** + * @brief Retrieves a path relative to the current module. + * + * Given a relative path, this function resolves it based on the directory + * of the current module, allowing for easy access to resources. + * + * @param relative_path The relative path to resolve. + * @return The resolved absolute path. + * + * @note If the module handle cannot be obtained, the function returns an empty path. + */ + std::filesystem::path get_path_relative_to_current_module(const std::filesystem::path& relative_path); + + /** + * @brief Extracts the filename from a given path. + * + * This function returns only the filename portion of a full path, excluding directories. + * + * @param path The full path as a string. + * @return The filename component of the provided path. + */ + std::string get_basename(const std::string& path); + + /** + * @brief Retrieves the full path to a module as a wide string. + * + * This function fetches the absolute path of a specified module, + * typically used for resolving file locations relative to the module. + * + * @param module The handle to the module. + * @return A wide string containing the full path to the specified module. + * @note The caller must free the returned string if dynamically allocated. + */ + std::wstring get_path_to_module_as_string(HMODULE module); +} // namespace pew::eos::io_helpers +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/json.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/json.h similarity index 100% rename from lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/json.h rename to lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/json.h diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/json_helpers.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/json_helpers.h new file mode 100644 index 000000000..6f8340df0 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/json_helpers.h @@ -0,0 +1,156 @@ +#ifndef JSON_HELPERS_H +#define JSON_HELPERS_H +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once +#include +#include + +#include "json.h" +#include "string_helpers.h" + +namespace std +{ + namespace filesystem + { + class path; + } +} + +namespace pew::eos::json_helpers +{ + /** + * \brief + * Collects flag values from either a JSON array of strings, or a + * comma-delimited list of values (like how Newtonsoft outputs things). + * + * \tparam T The type parameter (the enum type). + * + * \param + * strings_to_enum_values A collection that maps string values to enum values. + * + * \param + * default_value The default value to assign the flags if no matching string is + * found. + * + * \param + * iter The iterator into the json object element. + * + * \return A single flag value. + */ + template + static T collect_flags(const std::map* strings_to_enum_values, T default_value, json_object_element_s* iter) + { + T flags_to_return = static_cast(0); + bool flag_set = false; + + // Stores the string values that are within the JSON + std::vector string_values; + + // If the string values are stored as a JSON array of strings + if (iter->value->type == json_type_array) + { + // Do things if the type is an array + json_array_s* flags = json_value_as_array(iter->value); + for (auto e = flags->start; e != nullptr; e = e->next) + { + string_values.emplace_back(json_value_as_string(e->value)->string); + } + } + // If the string values are comma delimited + else if (iter->value->type == json_type_string) + { + const std::string flags = json_value_as_string(iter->value)->string; + string_values = string_helpers::split_and_trim(flags); + } + + // Iterate through the string values + for (const auto str : string_values) + { + // Skip if the string is not in the map + if (strings_to_enum_values->find(str.c_str()) == strings_to_enum_values->end()) + { + continue; + } + + // Otherwise, append the enum value + flags_to_return |= strings_to_enum_values->at(str.c_str()); + flag_set = true; + } + + return flag_set ? flags_to_return : default_value; + } + + /** + * @brief Parses a JSON value as a `double`, with a specified default. + * + * Attempts to interpret the provided JSON value as a `double`. If the value cannot be parsed as a number, + * it is treated as a string and parsed. If both attempts fail, the function returns the specified default. + * + * @param value The JSON value to interpret. + * @param default_value The value to return if parsing fails. + * @return The parsed `double` value, or `default_value` if parsing fails. + */ + double json_value_as_double(json_value_s* value, double default_value = 0.0); + + /** + * @brief Parses a JSON value as an unsigned 64-bit integer (`uint64_t`), with a specified default. + * + * Attempts to interpret the provided JSON value as a `uint64_t`. If the value cannot be parsed as a number, + * it is treated as a string and parsed. If both attempts fail, the function returns the specified default. + * + * @param value The JSON value to interpret. + * @param default_value The value to return if parsing fails. + * @return The parsed `uint64_t` value, or `default_value` if parsing fails. + */ + uint64_t json_value_as_uint64(json_value_s* value, uint64_t default_value = 0); + + /** + * @brief Parses a JSON value as an unsigned 32-bit integer (`uint32_t`), with a specified default. + * + * Attempts to interpret the provided JSON value as a `uint32_t`. If the value cannot be parsed as a number, + * it is treated as a string and parsed. If both attempts fail, the function returns the specified default. + * + * @param value The JSON value to interpret. + * @param default_value The value to return if parsing fails. + * @return The parsed `uint32_t` value, or `default_value` if parsing fails. + */ + uint32_t json_value_as_uint32(json_value_s* value, uint32_t default_value = 0); + + /** + * @brief Reads a JSON configuration file from a specified path and parses it. + * + * Opens and reads the specified configuration file, loads its contents into a buffer, + * parses the buffer as JSON, and returns the parsed JSON object. If the file is too large or + * cannot be opened, an exception is thrown. + * + * @param path_to_config_json The path to the JSON configuration file. + * @return A pointer to a `json_value_s` representing the parsed JSON structure, or `nullptr` on failure. + * + * @throws std::filesystem::filesystem_error If the file is too large to be processed. + * + * @note The caller is responsible for handling and freeing the parsed JSON structure as needed. + */ + json_value_s* read_config_json_as_json_from_path(std::filesystem::path path_to_config_json); +} +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/logging.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/logging.h new file mode 100644 index 000000000..37cbfa255 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/logging.h @@ -0,0 +1,196 @@ +#ifndef LOGGING_H +#define LOGGING_H +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include + +#include "PEW_EOS_Defines.h" + +/** + * \brief Forward declarations + */ +enum class EOS_ELogLevel; + +namespace pew::eos::logging +{ + typedef void (*log_flush_function_t)(const char* str); + + /** + * @brief Converts an EOS log level to its corresponding string representation. + * + * Maps an EOS log level enumeration to a string (e.g., "Fatal", "Error"). + * Returns `nullptr` if the log level is unrecognized. + * + * @param level The EOS log level to convert. + * @return The corresponding string representation, or `nullptr` if not found. + */ + const char* eos_loglevel_to_print_str(EOS_ELogLevel level); + + /** + * @brief Flushes buffered log messages using a custom flush function. + * + * Iterates over any buffered log messages and passes each one to the specified flush function. + * After flushing, the buffer is cleared. + * + * @param log_flush_function The function to call for each log message in the buffer. + */ + PEW_EOS_API_FUNC(void) global_log_flush_with_function(log_flush_function_t log_flush_function); + + /** + * @brief Converts a log level string to its corresponding EOS log level enumeration. + * + * Maps a string to an EOS log level enum value (e.g., "Error" to `EOS_LOG_Error`). + * Returns `EOS_LOG_Verbose` if the string is not found in the map. + * + * @param str The log level as a string. + * @return The corresponding EOS log level enum value, or `EOS_LOG_Verbose` if not found. + */ + EOS_ELogLevel eos_loglevel_str_to_enum(const std::string& str); + + /** + * @brief Displays a log message in a dialog box (Windows only). + * + * Opens a Windows message box displaying the provided log message as a warning. + * + * @param log_string The message to display in the dialog box. + */ + void show_log_as_dialog(const char* log_string); + + /** + * @brief Closes the global log file and clears the buffer. + * + * Closes the currently open log file (if any) and clears any buffered log messages. + */ + void global_log_close(); + + /** + * @brief Writes a formatted log message to the log file or buffer. + * + * Logs a formatted message to the open log file, if available, or to a buffer for deferred logging. + * Supports standard `printf` formatting. + * + * @param format The format string for the log message. + * @param ... Arguments for the format string. + */ + void global_logf(const char* format, ...); + + /** + * @brief EOS SDK log callback function. + * + * Receives log messages from the EOS SDK and logs them with a timestamp, category, + * and log level. + * + * @param message The log message provided by the EOS SDK. + */ + PEW_EOS_API_FUNC(void) EOS_CALL eos_log_callback(const EOS_LogMessage* message); + + /** + * @brief Opens a log file for writing. + * + * Opens a specified log file and writes any buffered log messages to it. If a log + * file is already open, it is closed before opening the new file. + * + * @param filename The name of the file to open for logging. + */ + void global_log_open(const char* filename); + + /** + * @brief Logs a message with a timestamp and header. + * + * Logs a message with the specified header (e.g., "WARNING", "ERROR"), + * and includes a timestamp if available. + * + * @param header The log header (e.g., "WARNING", "ERROR"). + * @param message The log message to record. + */ + void log_base(const char* header, const char* message); + + /** + * @brief Logs a warning message. + * + * Records a warning message with a "WARNING" header and, if enabled, + * displays it in a dialog box. + * + * @param log_string The warning message to log. + */ + void log_warn(const char* log_string); + + /** + * @brief Logs a warning message. + * + * Records a warning message with a "WARNING" header and, if enabled, + * displays it in a dialog box. + * + * @param log_string The warning message to log. + */ + inline void log_warn(const std::string& log_string) + { + return log_warn(log_string.c_str()); + } + + /** + * @brief Logs an informational message. + * + * Records an informational message with an "INFORM" header. + * + * @param log_string The informational message to log. + */ + void log_inform(const char* log_string); + + /** + * @brief Logs an informational message. + * + * Records an informational message with an "INFORM" header. + * + * @param log_string The informational message to log. + */ + inline void log_inform(const std::string& log_string) + { + return log_inform(log_string.c_str()); + } + + /** + * @brief Logs an error message. + * + * Records an error message with an "ERROR" header. + * + * @param log_string The error message to log. + */ + void log_error(const char* log_string); + + /** + * @brief Logs an error message. + * + * Records an error message with an "ERROR" header. + * + * @param log_string The error message to log. + */ + inline void log_error(const std::string& log_string) + { + return log_error(log_string.c_str()); + } +} +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/string_helpers.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/string_helpers.h new file mode 100644 index 000000000..b6ff1f223 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/include/string_helpers.h @@ -0,0 +1,154 @@ +#ifndef STRING_HELPERS_H +#define STRING_HELPERS_H +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once +#include +#include + +namespace std::filesystem +{ + class path; +} + +namespace pew::eos::string_helpers +{ + /** + * \brief Trims the whitespace from the beginning and end of a string. + * + * \param str The string to trim. + * + * \return A string with no whitespace at the beginning or end. + */ + std::string trim(const std::string& str); + + /** + * \brief + * Takes a string, splits it by the indicated delimiter, trims the results of + * the split, and returns a collection of any non-empty values. + * + * \param input The string to split and trim. + * + * \param delimiter The character at which to split the string. + * + * \return A list of string values. + */ + std::vector split_and_trim(const std::string& input, char delimiter = ','); + + /** + * @brief Creates an ISO 8601 formatted timestamp string with millisecond precision. + * + * This function generates a timestamp in the format "YYYY-MM-DDTHH:MM:SS.sss" and stores it + * in the provided buffer. If the buffer is not large enough to hold the timestamp, + * the function returns `false`. + * + * @param[out] final_timestamp A buffer to store the resulting timestamp string. + * @param[in] final_timestamp_len The length of the buffer provided. + * @return `true` if the timestamp was successfully created, `false` if the buffer was too small. + */ + bool create_timestamp_str(char* final_timestamp, size_t final_timestamp_len); + + /** + * @brief Calculates the number of bytes required to store a UTF-8 encoded version of a wide string. + * + * This function uses `WideCharToMultiByte` to determine the number of bytes necessary to represent + * a given wide string in UTF-8 encoding. + * + * @param[in] wide_str The wide string to evaluate. + * @param[in] wide_str_len The length of the wide string. + * @return The number of bytes required for UTF-8 encoding, or `0` if an error occurs. + */ + size_t utf8_str_bytes_required_for_wide_str(const wchar_t* wide_str, int wide_str_len); + + /** + * @brief Converts a wide string to a UTF-8 encoded string and copies it to a provided buffer. + * + * This function converts a wide character string to UTF-8 and stores the result in `utf8_str`. + * The buffer size should not exceed `INT_MAX` to avoid overflow. + * + * @param[out] utf8_str A buffer to store the UTF-8 encoded string. + * @param[in] utf8_str_len The length of the UTF-8 buffer. + * @param[in] wide_str The wide character string to convert. + * @param[in] wide_str_len The length of the wide string. If `wide_str` is null-terminated, set to -1. + * @return `true` if the conversion was successful, `false` otherwise. + * + * @note `wide_str` must be null-terminated if `wide_str_len` is set to -1. + */ + bool copy_to_utf8_str_from_wide_str(char* RESTRICT utf8_str, size_t utf8_str_len, const wchar_t* RESTRICT wide_str, int wide_str_len); + + /** + * @brief Creates a UTF-8 encoded string from a wide character string. + * + * This function allocates a UTF-8 encoded version of the provided wide character string. + * The caller is responsible for freeing the returned string. + * + * @param[in] wide_str The wide character string to convert. + * @return A dynamically allocated UTF-8 encoded string, or `nullptr` if conversion fails. + * + * @note The caller must free the returned string to avoid memory leaks. + */ + char* create_utf8_str_from_wide_str(const wchar_t* wide_str); + + /** + * @brief Creates a wide character string from a UTF-8 encoded string. + * + * Converts a UTF-8 string to a dynamically allocated wide character string. + * The caller is responsible for freeing the returned string. + * + * @param[in] utf8_str The UTF-8 encoded string to convert. + * @return A dynamically allocated wide string, or `nullptr` if conversion fails. + * + * @note The caller must free the returned string to avoid memory leaks. + */ + wchar_t* create_wide_str_from_utf8_str(const char* utf8_str); + + /** + * @brief Converts a wide string to a UTF-8 encoded `std::string`. + * + * Uses `std::wstring_convert` for the conversion. If `std::wstring_convert` + * becomes unsupported in the future, this function could be rewritten using + * `create_utf8_str_from_wide_str`. + * + * Using the std::wstring_convert method for this currently. It might be the + * case that in the future this method won't work. If that happens, one + * could convert this function to use the create_utf8_str_from_wide_str + * function to emulate it. Doing this might come with a cost, as data will + * need to be copied multiple times. + * + * @param[in] wide_str The wide string to convert. + * @return A UTF-8 encoded `std::string` representation of the wide string. + */ + std::string to_utf8_str(const std::wstring& wide_str); + + /** + * @brief Converts a filesystem path to a UTF-8 encoded `std::string`. + * + * This function provides a workaround for converting filesystem paths that contain kanji characters. + * Using this method ensures compatibility across different character sets. + * + * @param[in] path The filesystem path to convert. + * @return A UTF-8 encoded `std::string` representation of the path. + */ + std::string to_utf8_str(const std::filesystem::path& path); +} +#endif diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/pch.h b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/pch.h index 3a512177a..d1f1b628c 100644 --- a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/pch.h +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/pch.h @@ -8,20 +8,33 @@ #define PCH_H // add headers that you want to pre-compile here -#include "framework.h" - +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include #if _WIN32 || _WIN64 #define PLATFORM_WINDOWS 1 #endif -#if _WIN64 -#define PLATFORM_64BITS 1 -#define PLATFORM_BITS_DEBUG_STR "64-bits" -#else -#define PLATFORM_32BITS 1 -#define PLATFORM_BITS_DEBUG_STR "32-bits" +#define STATIC_EXPORT(return_type) extern "C" return_type + +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + +#if PLATFORM_WINDOWS +#include "processenv.h" #endif -#define STATIC_EXPORT(return_type) extern "C" return_type +#include "eos_sdk.h" +#include "Windows/eos_Windows.h" + +#define SHOW_DIALOG_BOX_ON_WARN 0 +#define ENABLE_DLL_BASED_EOS_CONFIG 1 +#define XAUDIO2_DLL_NAME "xaudio2_9redist.dll" + +#define EOS_SERVICE_CONFIG_FILENAME "EpicOnlineServicesConfig.json" +#define EOS_STEAM_CONFIG_FILENAME "eos_steam_config.json" +#define EOS_LOGLEVEL_CONFIG_FILENAME "log_level_config.json" + +#define RESTRICT __restrict + #endif //PCH_H diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/config_legacy.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/config_legacy.cpp new file mode 100644 index 000000000..779647708 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/config_legacy.cpp @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "config_legacy.h" +#include "eos_library_helpers.h" +#include "io_helpers.h" +#include "json_helpers.h" +#include "logging.h" + +using namespace pew::eos::config_legacy; +using namespace pew::eos::json_helpers; + + /** + * @brief Reads an EOS configuration file as a JSON value. + * + * Retrieves the specified configuration file, loads its contents, and parses it into a JSON structure. + * + * @param config_filename The name of the configuration file. + * @return A pointer to a `json_value_s` representing the configuration data, or `nullptr` if parsing fails. + */ +json_value_s* read_eos_config_as_json_value_from_file(std::string config_filename); + +EOSConfig eos_config_from_json_value(json_value_s* config_json) +{ + // Create platform instance + json_object_s* config_json_object = json_value_as_object(config_json); + json_object_element_s* iter = config_json_object->start; + EOSConfig eos_config; + + while (iter != nullptr) + { + if (!strcmp("productName", iter->name->string)) + { + eos_config.productName = json_value_as_string(iter->value)->string; + } + else if (!strcmp("productVersion", iter->name->string)) + { + eos_config.productVersion = json_value_as_string(iter->value)->string; + } + else if (!strcmp("productID", iter->name->string)) + { + eos_config.productID = json_value_as_string(iter->value)->string; + } + else if (!strcmp("sandboxID", iter->name->string)) + { + eos_config.sandboxID = json_value_as_string(iter->value)->string; + } + else if (!strcmp("deploymentID", iter->name->string)) + { + eos_config.deploymentID = json_value_as_string(iter->value)->string; + } + else if (!strcmp("sandboxDeploymentOverrides", iter->name->string)) + { + json_array_s* overrides = json_value_as_array(iter->value); + eos_config.sandboxDeploymentOverrides = std::vector(); + for (auto e = overrides->start; e != nullptr; e = e->next) + { + json_object_s* override_json_object = json_value_as_object(e->value); + json_object_element_s* ov_iter = override_json_object->start; + SandboxDeploymentOverride override_item = SandboxDeploymentOverride(); + while (ov_iter != nullptr) + { + if (!strcmp("sandboxID", ov_iter->name->string)) + { + override_item.sandboxID = json_value_as_string(ov_iter->value)->string; + } + else if (!strcmp("deploymentID", ov_iter->name->string)) + { + override_item.deploymentID = json_value_as_string(ov_iter->value)->string; + } + ov_iter = ov_iter->next; + } + eos_config.sandboxDeploymentOverrides.push_back(override_item); + } + } + else if (!strcmp("clientID", iter->name->string)) + { + eos_config.clientID = json_value_as_string(iter->value)->string; + } + else if (!strcmp("clientSecret", iter->name->string)) + { + eos_config.clientSecret = json_value_as_string(iter->value)->string; + } + if (!strcmp("encryptionKey", iter->name->string)) + { + eos_config.encryptionKey = json_value_as_string(iter->value)->string; + } + else if (!strcmp("overrideCountryCode ", iter->name->string)) + { + eos_config.overrideCountryCode = json_value_as_string(iter->value)->string; + } + else if (!strcmp("overrideLocaleCode", iter->name->string)) + { + eos_config.overrideLocaleCode = json_value_as_string(iter->value)->string; + } + else if (!strcmp("platformOptionsFlags", iter->name->string)) + { + eos_config.flags = static_cast(collect_flags(&PLATFORM_CREATION_FLAGS_STRINGS_TO_ENUM, 0, iter)); + } + else if (!strcmp("tickBudgetInMilliseconds", iter->name->string)) + { + eos_config.tickBudgetInMilliseconds = json_value_as_uint32(iter->value); + } + else if (!strcmp("taskNetworkTimeoutSeconds", iter->name->string)) + { + eos_config.taskNetworkTimeoutSeconds = json_value_as_double(iter->value); + } + else if (!strcmp("ThreadAffinity_networkWork", iter->name->string)) + { + eos_config.ThreadAffinity_networkWork = json_value_as_uint64(iter->value); + } + else if (!strcmp("ThreadAffinity_storageIO", iter->name->string)) + { + eos_config.ThreadAffinity_storageIO = json_value_as_uint64(iter->value); + } + else if (!strcmp("ThreadAffinity_webSocketIO", iter->name->string)) + { + eos_config.ThreadAffinity_webSocketIO = json_value_as_uint64(iter->value); + } + else if (!strcmp("ThreadAffinity_P2PIO", iter->name->string)) + { + eos_config.ThreadAffinity_P2PIO = json_value_as_uint64(iter->value); + } + else if (!strcmp("ThreadAffinity_HTTPRequestIO", iter->name->string)) + { + eos_config.ThreadAffinity_HTTPRequestIO = json_value_as_uint64(iter->value); + } + else if (!strcmp("ThreadAffinity_RTCIO", iter->name->string)) + { + eos_config.ThreadAffinity_RTCIO = json_value_as_uint64(iter->value); + } + else if (!strcmp("isServer", iter->name->string)) + { + // In this JSON library, true and false are _technically_ different types. + if (json_value_is_true(iter->value)) + { + eos_config.isServer = true; + } + else if (json_value_is_false(iter->value)) + { + eos_config.isServer = false; + } + } + + iter = iter->next; + } + + return eos_config; +} + +namespace pew::eos::config_legacy +{ + /** + * \brief Function that gets the config as a JSON string. + */ + static GetConfigAsJSONString_t GetConfigAsJSONString; + + CONFIG_API bool try_get_eos_config(EOSConfig& config) + { + auto path_to_config_json = get_path_for_eos_service_config(EOS_SERVICE_CONFIG_FILENAME); + json_value_s* eos_config_as_json = nullptr; + + eos_config_as_json = read_config_json_from_dll(); + + if (!eos_config_as_json && exists(path_to_config_json)) + { + eos_config_as_json = read_config_json_as_json_from_path(path_to_config_json); + } + + if (!eos_config_as_json) + { + logging::log_warn("Failed to load a valid json config for EOS"); + return false; + } + + config = eos_config_from_json_value(eos_config_as_json); + return true; + } + + + bool EOSSteamConfig::is_managed_by_application() const + { + return static_cast>(flags & + EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedByApplication); + } + + bool EOSSteamConfig::is_managed_by_sdk() const + { + return static_cast>(flags & + EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_LibraryManagedBySDK); + } + + LogLevelConfig log_config_from_json_value(json_value_s* config_json) + { + json_object_s* config_json_object = json_value_as_object(config_json); + json_object_element_s* iter = config_json_object->start; + LogLevelConfig log_config; + + while (iter != nullptr) + { + if (!strcmp("LogCategoryLevelPairs", iter->name->string)) + { + json_array_s* pairs = json_value_as_array(iter->value); + for (auto e = pairs->start; e != nullptr; e = e->next) + { + json_object_s* pairs_json_object = json_value_as_object(e->value); + json_object_element_s* pairs_iter = pairs_json_object->start; + while (pairs_iter != nullptr) + { + if (!strcmp("Category", pairs_iter->name->string)) + { + log_config.category.push_back(json_value_as_string(pairs_iter->value)->string); + } + else if (!strcmp("Level", pairs_iter->name->string)) + { + log_config.level.push_back(json_value_as_string(pairs_iter->value)->string); + } + pairs_iter = pairs_iter->next; + } + } + } + iter = iter->next; + } + + return log_config; + } + + std::filesystem::path get_path_for_eos_service_config(std::string config_filename) + { + return std::filesystem::path(CONFIG_DIRECTORY) / config_filename; + } + + json_value_s* read_config_json_from_dll() + { + json_value_s* config_json = nullptr; + +#if ENABLE_DLL_BASED_EOS_CONFIG + logging::log_inform("Trying to load eos config via dll"); + static void* eos_generated_library_handle = eos_library_helpers::load_library_at_path(io_helpers::get_path_relative_to_current_module("EOSGenerated.dll")); + + if (!eos_generated_library_handle) + { + logging::log_warn("No Generated DLL found (Might not be an error)"); + return NULL; + } + + GetConfigAsJSONString = eos_library_helpers::load_function_with_name(eos_generated_library_handle, "GetConfigAsJSONString"); + + if (GetConfigAsJSONString) + { + const char* config_as_json_string = GetConfigAsJSONString(); + if (config_as_json_string != nullptr) + { + size_t config_as_json_string_length = strlen(config_as_json_string); + config_json = json_parse(config_as_json_string, config_as_json_string_length); + } + } + else + { + logging::log_warn("No function found"); + } +#endif + + return config_json; + } + + EOS_EIntegratedPlatformManagementFlags eos_collect_integrated_platform_management_flags(json_object_element_s* iter) + { + return json_helpers::collect_flags( + &(INTEGRATED_PLATFORM_MANAGEMENT_FLAGS_STRINGS_TO_ENUM), + EOS_EIntegratedPlatformManagementFlags::EOS_IPMF_Disabled, + iter); + } + + EOSSteamConfig eos_steam_config_from_json_value(json_value_s* config_json) + { + json_object_s* config_json_object = json_value_as_object(config_json); + json_object_element_s* iter = config_json_object->start; + EOSSteamConfig eos_config; + + while (iter != nullptr) + { + if (!strcmp("flags", iter->name->string)) + { + eos_config.flags = eos_collect_integrated_platform_management_flags(iter); + + } + else if (!strcmp("overrideLibraryPath", iter->name->string)) + { + const char* override_library_path = json_value_as_string(iter->value)->string; + + if (strcmp("NULL", override_library_path) + && strcmp("null", override_library_path) + ) + { + eos_config.OverrideLibraryPath = override_library_path; + } + + } + else if (!strcmp("steamSDKMajorVersion", iter->name->string)) + { + eos_config.steamSDKMajorVersion = json_value_as_uint32(iter->value); + } + else if (!strcmp("steamSDKMinorVersion", iter->name->string)) + { + eos_config.steamSDKMinorVersion = json_value_as_uint32(iter->value); + } + else if (!strcmp("steamApiInterfaceVersionsArray", iter->name->string)) + { + json_array_s* apiVersions = json_value_as_array(iter->value); + + for (auto e = apiVersions->start; e != nullptr; e = e->next) + { + eos_config.steamApiInterfaceVersionsArray.push_back(json_value_as_string(e->value)->string); + } + } + + iter = iter->next; + } + + return eos_config; + } + + json_value_s* read_eos_config_as_json_value_from_file(std::string config_filename) + { + std::filesystem::path path_to_config_json = get_path_for_eos_service_config(config_filename); + + return read_config_json_as_json_from_path(path_to_config_json); + } +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/dllmain.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/dllmain.cpp new file mode 100644 index 000000000..50471c16d --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/dllmain.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// dllmain.cpp : Defines the entry point for the DLL application. +// This file does some *magick* to load the EOS Overlay DLL. +// This is apparently needed so that the Overlay can render properly +#include "pch.h" + +#include +#include + +#include "config_legacy.h" +#include "logging.h" +#include +#include +#include "io_helpers.h" + +using namespace pew::eos; +using namespace pew::eos::eos_library_helpers; + +using FSig_ApplicationWillShutdown = void (__stdcall *)(void); +FSig_ApplicationWillShutdown FuncApplicationWillShutdown = nullptr; + +extern "C" +{ + /** + * \brief Forward declaration and export for function to be called when + * Unity is loading the plugin. + */ + void __declspec(dllexport) __stdcall UnityPluginLoad(void* unityInterfaces); + + /** + * \brief Forward declaration for function to be called when Unity is + * unloading the plugin. + */ + void __declspec(dllexport) __stdcall UnityPluginUnload(); +} + +void get_cli_arguments(config_legacy::EOSConfig eos_config) +{ + //support sandbox and deployment id override via command line arguments + std::stringstream argument_stream = std::stringstream(GetCommandLineA()); + std::istream_iterator argument_stream_begin(argument_stream); + std::istream_iterator argument_stream_end; + std::vector argument_strings(argument_stream_begin, argument_stream_end); + std::string egsArgName = "-epicsandboxid="; + std::string sandboxArgName = "-eossandboxid="; + for (unsigned i = 0; i < argument_strings.size(); ++i) + { + std::string* match = nullptr; + if (argument_strings[i]._Starts_with(sandboxArgName)) + { + match = &sandboxArgName; + } + else if (argument_strings[i]._Starts_with(egsArgName)) + { + match = &egsArgName; + } + if (match != nullptr) + { + std::string sandboxArg = argument_strings[i].substr(match->length()); + if (!sandboxArg.empty()) + { + logging::log_inform(("Sandbox ID override specified: " + sandboxArg).c_str()); + eos_config.sandboxID = sandboxArg; + } + } + } + + //check if a deployment id override exists for sandbox id + for (unsigned i = 0; i < eos_config.sandboxDeploymentOverrides.size(); ++i) + { + if (eos_config.sandboxID == eos_config.sandboxDeploymentOverrides[i].sandboxID) + { + logging::log_inform(("Sandbox Deployment ID override specified: " + eos_config.sandboxDeploymentOverrides[i].deploymentID).c_str()); + eos_config.deploymentID = eos_config.sandboxDeploymentOverrides[i].deploymentID; + } + } + + std::string deploymentArgName = "-eosdeploymentid="; + std::string egsDeploymentArgName = "-epicdeploymentid="; + for (unsigned i = 0; i < argument_strings.size(); ++i) + { + std::string* match = nullptr; + if (argument_strings[i]._Starts_with(deploymentArgName)) + { + match = &deploymentArgName; + } + else if (argument_strings[i]._Starts_with(egsDeploymentArgName)) + { + match = &egsDeploymentArgName; + } + if (match != nullptr) + { + std::string deploymentArg = argument_strings[i].substr(match->length()); + if (!deploymentArg.empty()) + { + logging::log_inform(("Deployment ID override specified: " + deploymentArg).c_str()); + eos_config.deploymentID = deploymentArg; + } + } + } +} + +// Called by unity on load. It kicks off the work to load the DLL for Overlay +#if PLATFORM_32BITS +#pragma comment(linker, "/export:UnityPluginLoad=_UnityPluginLoad@4") +#endif +PEW_EOS_API_FUNC(void) UnityPluginLoad(void*) +{ +#if _DEBUG + logging::show_log_as_dialog("You may attach a debugger to the DLL"); +#endif + + config_legacy::EOSConfig eos_config; + if (!config_legacy::try_get_eos_config(eos_config)) + { + return; + } + + get_cli_arguments(eos_config); + +#if _DEBUG + logging::global_log_open("gfx_log.txt"); +#endif + + std::filesystem::path DllPath; + logging::log_inform("On UnityPluginLoad"); + + // Acquire pointer to EOS SDK library + s_eos_sdk_lib_handle = load_library_at_path(io_helpers::get_path_relative_to_current_module(SDK_DLL_NAME)); + + // If acquisition was successful. + if (s_eos_sdk_lib_handle) + { + // Make use of the library handle to get pointers to all the functions + // that are needed. + FetchEOSFunctionPointers(); + + // If the initialize function pointer is not null + if (EOS_Initialize_ptr) + { + logging::log_inform("start eos init"); + + eos_init(eos_config); + eos_set_loglevel_via_config(); + eos_create(eos_config); + + // Free function pointers and library handle. + s_eos_sdk_lib_handle = nullptr; + EOS_Initialize_ptr = nullptr; + EOS_Shutdown_ptr = nullptr; + EOS_Platform_Create_ptr = nullptr; + } + else + { + logging::log_warn("unable to find EOS_Initialize"); + } + } + else + { + logging::log_warn("Couldn't find dll " SDK_DLL_NAME); + } +} + +//------------------------------------------------------------------------- +#if PLATFORM_32BITS +#pragma comment(linker, "/export:_UnityPluginUnload=_UnityPluginUnload@0") +#endif +PEW_EOS_API_FUNC(void) UnityPluginUnload() +{ + if (FuncApplicationWillShutdown != nullptr) + { + FuncApplicationWillShutdown(); + } + unload_library(s_eos_sdk_overlay_lib_handle); + s_eos_sdk_overlay_lib_handle = nullptr; + + logging::global_log_close(); +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/eos_helpers.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/eos_helpers.cpp new file mode 100644 index 000000000..6a1118ac2 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/eos_helpers.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "eos_helpers.h" +#include +#include +#include "config_legacy.h" +#include "eos_library_helpers.h" +#include "io_helpers.h" +#include "json_helpers.h" +#include "logging.h" +#include + + /** + * @brief Retrieves the system cache directory. + * + * Retrieves the system's temporary directory and converts it to a UTF-8 encoded string. + * + * @return A pointer to a UTF-8 encoded string containing the system cache directory. + * This pointer is statically allocated, so it should not be freed by the caller. + */ +char* GetCacheDirectory(); + +/** + * @brief Loads and initializes the Steam API DLL using a string path. + * + * Attempts to load the Steam API DLL from the specified path. If the DLL is not already + * loaded, this function tries to load it and then calls `SteamAPI_Init`. + * + * @param steam_dll_path The string path to the Steam API DLL. + */ +void eos_call_steam_init(const std::string& steam_dll_path); + +/** + * @brief Loads the Steam API DLL from a specified path. + * + * Loads the Steam API DLL and initializes it if necessary. Attempts to load the DLL from + * the specified path, or defaults to `steam_api.dll` if no path is specified. + * + * This function assumes that if the caller has already loaded the steam + * DLL, that SteamAPI_Init doesn't need to be called + * + * @param steam_dll_path The path to the Steam API DLL. + */ +void eos_call_steam_init(const std::filesystem::path& steam_dll_path); + +namespace pew::eos +{ + PEW_EOS_API_FUNC(void*) EOS_GetPlatformInterface() + { + return eos_library_helpers::eos_platform_handle; + } + + void eos_set_loglevel_via_config() + { + if (eos_library_helpers::EOS_Logging_SetLogLevel_ptr == nullptr) + { + return; + } + + auto path_to_log_config_json = config_legacy::get_path_for_eos_service_config(EOS_LOGLEVEL_CONFIG_FILENAME); + + if (!exists(path_to_log_config_json)) + { + logging::log_inform("Log level config not found, using default log levels"); + return; + } + + json_value_s* log_config_as_json = json_helpers::read_config_json_as_json_from_path(path_to_log_config_json); + config_legacy::LogLevelConfig log_config = config_legacy::log_config_from_json_value(log_config_as_json); + free(log_config_as_json); + + // Validation to prevent out of range exception + if (log_config.category.size() != log_config.level.size()) + { + logging::log_warn("Log level config entries out of range"); + return; + } + + // Last in the vector is AllCategories, and will not be set + size_t individual_category_size = log_config.category.size() > 0 ? log_config.category.size() - 1 : 0; + if (individual_category_size == 0) + { + logging::log_warn("Log level config entries empty"); + return; + } + + for (size_t i = 0; i < individual_category_size; i++) + { + eos_library_helpers::EOS_Logging_SetLogLevel_ptr((EOS_ELogCategory)i, logging::eos_loglevel_str_to_enum(log_config.level[i])); + } + + logging::log_inform("Log levels set according to config"); + } + + void EOS_Platform_Options_debug_log(const EOS_Platform_Options& platform_options) + { + std::stringstream output; + output << platform_options.ApiVersion << "\n"; + output << platform_options.bIsServer << "\n"; + output << platform_options.Flags << "\n"; + output << platform_options.CacheDirectory << "\n"; + + output << platform_options.EncryptionKey << "\n"; + if (platform_options.OverrideCountryCode) + { + output << platform_options.OverrideCountryCode << "\n"; + } + + if (platform_options.OverrideLocaleCode) + { + output << platform_options.OverrideLocaleCode << "\n"; + } + output << platform_options.ProductId << "\n"; + output << platform_options.SandboxId << "\n"; + output << platform_options.DeploymentId << "\n"; + output << platform_options.ClientCredentials.ClientId << "\n"; + output << platform_options.ClientCredentials.ClientSecret << "\n"; + + auto* rtc_options = platform_options.RTCOptions; + auto* windows_rtc_options = (EOS_Windows_RTCOptions*)rtc_options->PlatformSpecificOptions; + + output << windows_rtc_options->ApiVersion << "\n"; + output << windows_rtc_options->XAudio29DllPath << "\n"; + + logging::log_inform(output.str().c_str()); + } + + void eos_init(const config_legacy::EOSConfig eos_config) + { + static int reserved[2] = { 1, 1 }; + EOS_InitializeOptions SDKOptions = { 0 }; + SDKOptions.ApiVersion = EOS_INITIALIZE_API_LATEST; + SDKOptions.AllocateMemoryFunction = nullptr; + SDKOptions.ReallocateMemoryFunction = nullptr; + SDKOptions.ReleaseMemoryFunction = nullptr; + SDKOptions.ProductName = eos_config.productName.c_str(); + SDKOptions.ProductVersion = eos_config.productVersion.c_str(); + SDKOptions.Reserved = reserved; + SDKOptions.SystemInitializeOptions = nullptr; + + EOS_Initialize_ThreadAffinity overrideThreadAffinity = { 0 }; + + overrideThreadAffinity.ApiVersion = EOS_INITIALIZE_THREADAFFINITY_API_LATEST; + + overrideThreadAffinity.HttpRequestIo = eos_config.ThreadAffinity_HTTPRequestIO; + overrideThreadAffinity.NetworkWork = eos_config.ThreadAffinity_networkWork; + overrideThreadAffinity.P2PIo = eos_config.ThreadAffinity_P2PIO; + overrideThreadAffinity.RTCIo = eos_config.ThreadAffinity_RTCIO; + overrideThreadAffinity.StorageIo = eos_config.ThreadAffinity_storageIO; + overrideThreadAffinity.WebSocketIo = eos_config.ThreadAffinity_webSocketIO; + + + SDKOptions.OverrideThreadAffinity = &overrideThreadAffinity; + + logging::log_inform("call EOS_Initialize"); + EOS_EResult InitResult = eos_library_helpers::EOS_Initialize_ptr(&SDKOptions); + if (InitResult != EOS_EResult::EOS_Success) + { + logging::log_error("Unable to do eos init"); + } + if (eos_library_helpers::EOS_Logging_SetLogLevel_ptr != nullptr) + { + eos_library_helpers::EOS_Logging_SetLogLevel_ptr(EOS_ELogCategory::EOS_LC_ALL_CATEGORIES, EOS_ELogLevel::EOS_LOG_VeryVerbose); + } + + if (eos_library_helpers::EOS_Logging_SetCallback_ptr != nullptr) + { + eos_library_helpers::EOS_Logging_SetCallback_ptr(&logging::eos_log_callback); + } + } + + static void eos_call_steam_init(const std::filesystem::path& steam_dll_path) + { + std::string steam_dll_path_as_string = steam_dll_path.string(); + eos_call_steam_init(steam_dll_path_as_string); + } + + static void eos_call_steam_init(const std::string& steam_dll_path) + { + auto steam_dll_path_string = io_helpers::get_basename(steam_dll_path); + HANDLE steam_dll_handle = GetModuleHandleA(steam_dll_path_string.c_str()); + + // Check the default name for the steam_api.dll + if (!steam_dll_handle) + { + steam_dll_handle = GetModuleHandleA("steam_api.dll"); + } + + // in the case that it's not loaded, try to load it from the user provided path + if (!steam_dll_handle) + { + steam_dll_handle = eos_library_helpers::load_library_at_path(steam_dll_path); + } + + if (steam_dll_handle != nullptr) + { + typedef bool(__cdecl* SteamAPI_Init_t)(); + SteamAPI_Init_t SteamAPI_Init = pew::eos::eos_library_helpers::load_function_with_name(steam_dll_handle, "SteamAPI_Init"); + + if (SteamAPI_Init()) + { + logging::log_inform("Called SteamAPI_Init with success!"); + } + } + } + + char* GetCacheDirectory() + { + static char* s_tempPathBuffer = NULL; + + if (s_tempPathBuffer == NULL) + { + WCHAR tmp_buffer = 0; + DWORD buffer_size = GetTempPathW(1, &tmp_buffer) + 1; + WCHAR* lpTempPathBuffer = (TCHAR*)malloc(buffer_size * sizeof(TCHAR)); + GetTempPathW(buffer_size, lpTempPathBuffer); + + s_tempPathBuffer = string_helpers::create_utf8_str_from_wide_str(lpTempPathBuffer); + free(lpTempPathBuffer); + } + + return s_tempPathBuffer; + } + + void eos_create(config_legacy::EOSConfig eos_config) + { + EOS_Platform_Options platform_options = { 0 }; + platform_options.ApiVersion = EOS_PLATFORM_OPTIONS_API_LATEST; + platform_options.bIsServer = eos_config.isServer; + platform_options.Flags = eos_config.flags; + platform_options.CacheDirectory = GetCacheDirectory(); + + platform_options.EncryptionKey = eos_config.encryptionKey.length() > 0 ? eos_config.encryptionKey.c_str() : nullptr; + platform_options.OverrideCountryCode = eos_config.overrideCountryCode.length() > 0 ? eos_config.overrideCountryCode.c_str() : nullptr; + platform_options.OverrideLocaleCode = eos_config.overrideLocaleCode.length() > 0 ? eos_config.overrideLocaleCode.c_str() : nullptr; + platform_options.ProductId = eos_config.productID.c_str(); + platform_options.SandboxId = eos_config.sandboxID.c_str(); + platform_options.DeploymentId = eos_config.deploymentID.c_str(); + platform_options.ClientCredentials.ClientId = eos_config.clientID.c_str(); + platform_options.ClientCredentials.ClientSecret = eos_config.clientSecret.c_str(); + + platform_options.TickBudgetInMilliseconds = eos_config.tickBudgetInMilliseconds; + + if (eos_config.taskNetworkTimeoutSeconds > 0) + { + platform_options.TaskNetworkTimeoutSeconds = &eos_config.taskNetworkTimeoutSeconds; + } + + EOS_Platform_RTCOptions rtc_options = { 0 }; + + rtc_options.ApiVersion = EOS_PLATFORM_RTCOPTIONS_API_LATEST; +#if PLATFORM_WINDOWS + logging::log_inform("setting up rtc"); + std::filesystem::path xaudio2_dll_path = io_helpers::get_path_relative_to_current_module(XAUDIO2_DLL_NAME); + std::string xaudio2_dll_path_as_string = string_helpers::to_utf8_str(xaudio2_dll_path); + EOS_Windows_RTCOptions windows_rtc_options = { 0 }; + windows_rtc_options.ApiVersion = EOS_WINDOWS_RTCOPTIONS_API_LATEST; + windows_rtc_options.XAudio29DllPath = xaudio2_dll_path_as_string.c_str(); + logging::log_warn(xaudio2_dll_path_as_string.c_str()); + + if (!std::filesystem::exists(xaudio2_dll_path)) + { + logging::log_warn("Missing XAudio dll!"); + } + rtc_options.PlatformSpecificOptions = &windows_rtc_options; + platform_options.RTCOptions = &rtc_options; +#endif + +#if PLATFORM_WINDOWS + auto path_to_steam_config_json = config_legacy::get_path_for_eos_service_config(EOS_STEAM_CONFIG_FILENAME); + + // Defined here so that the override path lives long enough to be referenced by the create option + config_legacy::EOSSteamConfig eos_steam_config; + EOS_IntegratedPlatform_Options steam_integrated_platform_option = { 0 }; + EOS_IntegratedPlatform_Steam_Options steam_platform = { 0 }; + EOS_HIntegratedPlatformOptionsContainer integrated_platform_options_container = nullptr; + std::wstring_convert> converter; + + if (exists(path_to_steam_config_json)) + { + json_value_s* eos_steam_config_as_json = nullptr; + eos_steam_config_as_json = json_helpers::read_config_json_as_json_from_path(path_to_steam_config_json); + eos_steam_config = config_legacy::eos_steam_config_from_json_value(eos_steam_config_as_json); + free(eos_steam_config_as_json); + + if (eos_steam_config.OverrideLibraryPath.has_value()) + { + if (!std::filesystem::exists(eos_steam_config.OverrideLibraryPath.value())) + { + auto override_lib_path_as_str = io_helpers::get_basename(eos_steam_config.OverrideLibraryPath.value()); + auto found_steam_path = io_helpers::get_path_relative_to_current_module(override_lib_path_as_str); + + // Fall back and use the steam dll name based on the + // type of binary the GfxPluginNativeRender + if (!std::filesystem::exists(found_steam_path) || eos_steam_config.OverrideLibraryPath.value().empty()) + { + found_steam_path = io_helpers::get_path_relative_to_current_module(STEAM_API_DLL); + } + + if (std::filesystem::exists(found_steam_path)) + { + eos_steam_config.OverrideLibraryPath = converter.to_bytes(found_steam_path.wstring()); + } + } + } + else + { + auto found_steam_path = io_helpers::get_path_relative_to_current_module(STEAM_API_DLL); + if (exists(found_steam_path)) + { + eos_steam_config.OverrideLibraryPath = converter.to_bytes(found_steam_path.wstring()); + } + } + + if (eos_steam_config.is_managed_by_application()) + { + eos_call_steam_init(eos_steam_config.OverrideLibraryPath.value()); + eos_steam_config.OverrideLibraryPath.reset(); + } + + if (eos_steam_config.OverrideLibraryPath.has_value()) + { + steam_platform.OverrideLibraryPath = eos_steam_config.OverrideLibraryPath.value().c_str(); + } + + steam_platform.SteamMajorVersion = eos_steam_config.steamSDKMajorVersion; + steam_platform.SteamMinorVersion = eos_steam_config.steamSDKMinorVersion; + + // For each element in the array (each of which is a string of an api version information) + // iterate across each character, and at the end of a string add a null terminator \0 + // then add one more null terminator at the end of the array + std::vector steamApiInterfaceVersionsAsCharArray; + + for (const auto& currentFullValue : eos_steam_config.steamApiInterfaceVersionsArray) + { + for (char currentCharacter : currentFullValue) + { + steamApiInterfaceVersionsAsCharArray.push_back(currentCharacter); + } + + steamApiInterfaceVersionsAsCharArray.push_back('\0'); + } + steamApiInterfaceVersionsAsCharArray.push_back('\0'); + + steam_platform.SteamApiInterfaceVersionsArray = reinterpret_cast(steamApiInterfaceVersionsAsCharArray.data()); + + auto size = steamApiInterfaceVersionsAsCharArray.size(); + + if (size > EOS_INTEGRATEDPLATFORM_STEAM_MAX_STEAMAPIINTERFACEVERSIONSARRAY_SIZE) + { + logging::log_error("Size given for SteamApiInterfaceVersionsAsCharArray exceeds the maximum value."); + } + else + { + // steam_platform needs to have a count of how many bytes the "array" is, stored in SteamApiInterfaceVersionsArrayBytes + // This has some fuzzy behavior; if you set it to 0 or count it up properly, there won't be a logged problem + // if you put a non-zero amount that is insufficient, there will be an unclear logged error message + steam_platform.SteamApiInterfaceVersionsArrayBytes = static_cast(size); + } + + steam_integrated_platform_option.ApiVersion = EOS_INTEGRATEDPLATFORM_OPTIONS_API_LATEST; + steam_integrated_platform_option.Type = EOS_IPT_Steam; + steam_integrated_platform_option.Flags = eos_steam_config.flags; + steam_integrated_platform_option.InitOptions = &steam_platform; + + steam_platform.ApiVersion = EOS_INTEGRATEDPLATFORM_STEAM_OPTIONS_API_LATEST; + + EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainerOptions options = { EOS_INTEGRATEDPLATFORM_CREATEINTEGRATEDPLATFORMOPTIONSCONTAINER_API_LATEST }; + eos_library_helpers::EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr(&options, &integrated_platform_options_container); + platform_options.IntegratedPlatformOptionsContainerHandle = integrated_platform_options_container; + + EOS_IntegratedPlatformOptionsContainer_AddOptions addOptions = { EOS_INTEGRATEDPLATFORMOPTIONSCONTAINER_ADD_API_LATEST }; + addOptions.Options = &steam_integrated_platform_option; + eos_library_helpers::EOS_IntegratedPlatformOptionsContainer_Add_ptr(integrated_platform_options_container, &addOptions); + } +#endif + + //EOS_Platform_Options_debug_log(platform_options); + logging::log_inform("run EOS_Platform_Create"); + eos_library_helpers::eos_platform_handle = eos_library_helpers::EOS_Platform_Create_ptr(&platform_options); + if (integrated_platform_options_container) + { + eos_library_helpers::EOS_IntegratedPlatformOptionsContainer_Release_ptr(integrated_platform_options_container); + } + + if (!eos_library_helpers::eos_platform_handle) + { + logging::log_error("failed to create the platform"); + } + } +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/eos_library_helpers.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/eos_library_helpers.cpp new file mode 100644 index 000000000..f7efcefce --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/eos_library_helpers.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "eos_library_helpers.h" +#include "logging.h" +#include "string_helpers.h" + +/** + * @brief Chooses a string based on the platform's bitness. + * + * Returns `choice_if_32bit` if the platform is 32-bit; otherwise, returns `choice_if_else`. + * + * @param choice_if_32bit The string to return if the platform is 32-bit. + * @param choice_if_else The string to return otherwise. + * @return The appropriate string based on the platform bitness. + */ +const char* pick_if_32bit_else(const char* choice_if_32bit, const char* choice_if_else) +{ +#if PLATFORM_32BITS + return choice_if_32bit; +#else + return choice_if_else; +#endif +} + +namespace pew::eos::eos_library_helpers +{ + typedef void (EOS_CALL* EOS_Platform_Release_t)(EOS_HPlatform Handle); + + EOS_Platform_Release_t EOS_Platform_Release_ptr; + + void* s_eos_sdk_lib_handle = nullptr; + void* s_eos_sdk_overlay_lib_handle = nullptr; + EOS_HPlatform eos_platform_handle = nullptr; + + EOS_Initialize_t EOS_Initialize_ptr = nullptr; + EOS_Shutdown_t EOS_Shutdown_ptr = nullptr; + EOS_Platform_Create_t EOS_Platform_Create_ptr = nullptr; + EOS_Logging_SetCallback_t EOS_Logging_SetCallback_ptr = nullptr; + EOS_Logging_SetLogLevel_t EOS_Logging_SetLogLevel_ptr = nullptr; + EOS_IntegratedPlatformOptionsContainer_Add_t EOS_IntegratedPlatformOptionsContainer_Add_ptr = nullptr; + EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_t EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr = nullptr; + EOS_IntegratedPlatformOptionsContainer_Release_t EOS_IntegratedPlatformOptionsContainer_Release_ptr = nullptr; + + void* load_library_at_path(const std::filesystem::path& library_path) + { + void* to_return = nullptr; + +#if PLATFORM_WINDOWS + logging::log_inform(("Loading path at " + string_helpers::to_utf8_str(library_path)).c_str()); + HMODULE handle = LoadLibrary(library_path.c_str()); + to_return = (void*)handle; +#endif + + return to_return; + } + + void* load_function_with_name(void* library_handle, const char* function) + { + void* to_return = nullptr; +#if PLATFORM_WINDOWS + HMODULE handle = (HMODULE)library_handle; + to_return = (void*)GetProcAddress(handle, function); +#endif + return to_return; + } + + void FetchEOSFunctionPointers() + { + // The '@' in the function names is apart of how names are mangled on windows. The value after the '@' is the size of the params on the stack + EOS_Initialize_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Initialize@4", "EOS_Initialize")); + EOS_Shutdown_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Shutdown@0", "EOS_Shutdown")); + EOS_Platform_Create_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Platform_Create@4", "EOS_Platform_Create")); + EOS_Platform_Release_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Platform_Release@4", "EOS_Platform_Release")); + EOS_Logging_SetLogLevel_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_Logging_SetLogLevel@8", "EOS_Logging_SetLogLevel")); + EOS_Logging_SetCallback_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("EOS_Logging_SetCallback@4", "EOS_Logging_SetCallback")); + + EOS_IntegratedPlatformOptionsContainer_Add_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_IntegratedPlatformOptionsContainer_Add@8", "EOS_IntegratedPlatformOptionsContainer_Add")); + EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer@8", "EOS_IntegratedPlatform_CreateIntegratedPlatformOptionsContainer")); + EOS_IntegratedPlatformOptionsContainer_Release_ptr = load_function_with_name(s_eos_sdk_lib_handle, pick_if_32bit_else("_EOS_IntegratedPlatformOptionsContainer_Release@4", "EOS_IntegratedPlatformOptionsContainer_Release")); + } + + bool QueryRegKey(const HKEY InKey, const TCHAR* InSubKey, const TCHAR* InValueName, std::wstring& OutData) + { + bool bSuccess = false; +#if PLATFORM_WINDOWS + // Redirect key depending on system + for (uint32_t RegistryIndex = 0; RegistryIndex < 2 && !bSuccess; ++RegistryIndex) + { + HKEY Key = 0; + const uint32_t RegFlags = (RegistryIndex == 0) ? KEY_WOW64_32KEY : KEY_WOW64_64KEY; + if (RegOpenKeyEx(InKey, InSubKey, 0, KEY_READ | RegFlags, &Key) == ERROR_SUCCESS) + { + DWORD Size = 0; + // First, we'll call RegQueryValueEx to find out how large of a buffer we need + if ((RegQueryValueEx(Key, InValueName, NULL, NULL, NULL, &Size) == ERROR_SUCCESS) && Size) + { + // Allocate a buffer to hold the value and call the function again to get the data + char* Buffer = new char[Size]; + if (RegQueryValueEx(Key, InValueName, NULL, NULL, (LPBYTE)Buffer, &Size) == ERROR_SUCCESS) + { + const uint32_t Length = (Size / sizeof(TCHAR)) - 1; + OutData = (TCHAR*)Buffer; + bSuccess = true; + } + delete[] Buffer; + } + RegCloseKey(Key); + } + } +#endif + return bSuccess; + } + + void unload_library(void* library_handle) + { + FreeLibrary((HMODULE)library_handle); + } + + static bool get_overlay_dll_path(std::filesystem::path* OutDllPath) + { +#if PLATFORM_WINDOWS + const TCHAR* RegKey = TEXT(R"(SOFTWARE\Epic Games\EOS)"); + const TCHAR* RegValue = TEXT("OverlayPath"); + std::wstring OverlayDllDirectory; + + if (!QueryRegKey(HKEY_CURRENT_USER, RegKey, RegValue, OverlayDllDirectory)) + { + if (!QueryRegKey(HKEY_LOCAL_MACHINE, RegKey, RegValue, OverlayDllDirectory)) + { + return false; + } + } + + *OutDllPath = std::filesystem::path(OverlayDllDirectory) / OVERLAY_DLL_NAME; + return exists(*OutDllPath) && is_regular_file(*OutDllPath); +#else + log_inform("Trying to get a DLL path on a platform without DLL paths searching"); + return false; +#endif + } +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/io_helpers.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/io_helpers.cpp new file mode 100644 index 000000000..16272f10b --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/io_helpers.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +namespace pew::eos::io_helpers +{ + TCHAR* get_path_to_module(HMODULE module) + { + DWORD module_path_length = 128; + TCHAR* module_path = static_cast(malloc(module_path_length * sizeof(TCHAR))); + + if (!module_path) { + return nullptr; // Failed to allocate memory + } + + while (true) { + DWORD buffer_length = GetModuleFileName(module, module_path, module_path_length); + + if (buffer_length > 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + // Successfully retrieved path + break; + } + + // Handle insufficient buffer case + module_path_length += 20; + TCHAR* new_module_path = static_cast(realloc(module_path, module_path_length * sizeof(TCHAR))); + if (!new_module_path) { + free(module_path); + return nullptr; // Memory allocation failure + } + module_path = new_module_path; + } + + return module_path; + } + + std::wstring get_path_to_module_as_string(HMODULE module) + { + wchar_t* module_path = get_path_to_module(module); + + std::wstring module_file_path_string(module_path); + free(module_path); + return module_file_path_string; + } + + std::filesystem::path get_path_relative_to_current_module(const std::filesystem::path& relative_path) + { + HMODULE this_module = nullptr; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)&get_path_relative_to_current_module, &this_module) || !this_module) { + return {}; + } + + std::wstring module_file_path_string = get_path_to_module_as_string(this_module); + return std::filesystem::path(module_file_path_string).remove_filename() / relative_path; + } + + std::string get_basename(const std::string& path) + { + std::string filename; + filename.resize(path.length() + 1); + _splitpath_s(path.c_str(), NULL, 0, NULL, 0, filename.data(), filename.size(), NULL, 0); + + return filename; + } +} // namespace pew::eos::io_helpers diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/json_helpers.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/json_helpers.cpp new file mode 100644 index 000000000..2afc3b6f6 --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/json_helpers.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "json_helpers.h" +#include +#include "logging.h" + +namespace pew::eos::json_helpers +{ + double json_value_as_double(json_value_s* value, double default_value) + { + double val = 0.0; + json_number_s* n = json_value_as_number(value); + + if (n != nullptr) + { + char* end = nullptr; + val = strtod(n->number, &end); + } + else + { + // try to treat it as a string, then parse as long + char* end = nullptr; + json_string_s* val_as_str = json_value_as_string(value); + + if (val_as_str == nullptr || strlen(val_as_str->string) == 0) + { + val = default_value; + } + else + { + val = strtod(val_as_str->string, &end); + } + } + + return val; + } + + uint64_t json_value_as_uint64(json_value_s* value, uint64_t default_value) + { + uint64_t val = 0; + json_number_s* n = json_value_as_number(value); + + if (n != nullptr) + { + char* end = nullptr; + val = strtoull(n->number, &end, 10); + } + else + { + // try to treat it as a string, then parse as long + char* end = nullptr; + json_string_s* val_as_str = json_value_as_string(value); + if (val_as_str == nullptr || strlen(val_as_str->string) == 0) + { + val = default_value; + } + else + { + val = strtoull(val_as_str->string, &end, 10); + } + } + + return val; + } + + uint32_t json_value_as_uint32(json_value_s* value, uint32_t default_value) + { + uint32_t val = 0; + json_number_s* n = json_value_as_number(value); + + if (n != nullptr) + { + char* end = nullptr; + val = strtoul(n->number, &end, 10); + } + else + { + // try to treat it as a string, then parse as long + char* end = nullptr; + json_string_s* val_as_str = json_value_as_string(value); + + if (val_as_str == nullptr || strlen(val_as_str->string) == 0) + { + val = default_value; + } + else + { + val = strtoul(val_as_str->string, &end, 10); + } + } + + return val; + } + + json_value_s* read_config_json_as_json_from_path(std::filesystem::path path_to_config_json) + { + logging::log_inform(("json path" + string_helpers::to_utf8_str(path_to_config_json)).c_str()); + const size_t config_file_size = static_cast(file_size(path_to_config_json)); + if (config_file_size > SIZE_MAX) + { + throw std::filesystem::filesystem_error("File is too large", std::make_error_code(std::errc::file_too_large)); + } + + FILE* file = nullptr; + errno_t config_file_error = _wfopen_s(&file, path_to_config_json.wstring().c_str(), L"r"); + char* buffer = static_cast(calloc(1, config_file_size)); + + const size_t bytes_read = fread(buffer, 1, config_file_size, file); + fclose(file); + struct json_value_s* config_json = json_parse(buffer, bytes_read); + free(buffer); + + return config_json; + } +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/logging.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/logging.cpp new file mode 100644 index 000000000..700548f7d --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/logging.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "logging.h" +#include +#include "string_helpers.h" +#include + +namespace pew::eos::logging +{ + FILE* s_log_file = nullptr; + std::vector buffered_output; + + const std::unordered_map LOGLEVEL_STR_MAP = + { + {"Off",EOS_ELogLevel::EOS_LOG_Off}, + {"Fatal",EOS_ELogLevel::EOS_LOG_Fatal}, + {"Error",EOS_ELogLevel::EOS_LOG_Error}, + {"Warning",EOS_ELogLevel::EOS_LOG_Warning}, + {"Info",EOS_ELogLevel::EOS_LOG_Info}, + {"Verbose",EOS_ELogLevel::EOS_LOG_Verbose}, + {"VeryVerbose",EOS_ELogLevel::EOS_LOG_VeryVerbose}, + }; + + const char* eos_loglevel_to_print_str(const EOS_ELogLevel level) + { + switch (level) + { + case EOS_ELogLevel::EOS_LOG_Off: + return "Off"; + case EOS_ELogLevel::EOS_LOG_Fatal: + return "Fatal"; + case EOS_ELogLevel::EOS_LOG_Error: + return "Error"; + case EOS_ELogLevel::EOS_LOG_Warning: + return "Warning"; + case EOS_ELogLevel::EOS_LOG_Info: + return "Info"; + case EOS_ELogLevel::EOS_LOG_Verbose: + return "Verbose"; + case EOS_ELogLevel::EOS_LOG_VeryVerbose: + return "VeryVerbose"; + default: + return nullptr; + } + } + + PEW_EOS_API_FUNC(void) global_log_flush_with_function(const log_flush_function_t log_flush_function) + { + if (!buffered_output.empty()) + { + for (const std::string& str : buffered_output) + { + log_flush_function(str.c_str()); + } + buffered_output.clear(); + } + } + + EOS_ELogLevel eos_loglevel_str_to_enum(const std::string& str) + { + auto it = LOGLEVEL_STR_MAP.find(str); + if (it != LOGLEVEL_STR_MAP.end()) + { + return it->second; + } + return EOS_ELogLevel::EOS_LOG_Verbose; + } + + void show_log_as_dialog(const char* log_string) + { +#if PLATFORM_WINDOWS + MessageBoxA(nullptr, log_string, "Warning", MB_ICONWARNING); +#endif + } + + void global_log_close() + { + if (s_log_file) + { + fclose(s_log_file); + s_log_file = nullptr; + buffered_output.clear(); + } + } + + void global_logf(const char* format, ...) + { + if (s_log_file != nullptr) + { + va_list arg_list; + va_start(arg_list, format); + vfprintf(s_log_file, format, arg_list); + va_end(arg_list); + + fprintf(s_log_file, "\n"); + fflush(s_log_file); + } + else + { + va_list arg_list; + va_start(arg_list, format); + va_list arg_list_copy; + va_copy(arg_list_copy, arg_list); + const size_t printed_length = vsnprintf(nullptr, 0, format, arg_list) + 1; + va_end(arg_list); + + std::vector buffer(printed_length); + vsnprintf(buffer.data(), printed_length, format, arg_list_copy); + va_end(arg_list_copy); + buffered_output.emplace_back(std::string(buffer.data(), printed_length)); + } + } + + PEW_EOS_API_FUNC(void) EOS_CALL eos_log_callback(const EOS_LogMessage* message) + { + constexpr size_t final_timestamp_len = 32; + char final_timestamp[final_timestamp_len] = { 0 }; + + if (string_helpers::create_timestamp_str(final_timestamp, final_timestamp_len)) + { + global_logf("%s %s (%s): %s", final_timestamp, message->Category, eos_loglevel_to_print_str(message->Level), message->Message); + } + else + { + global_logf("%s (%s): %s", message->Category, eos_loglevel_to_print_str(message->Level), message->Message); + } + } + + void global_log_open(const char* filename) + { + if (s_log_file != nullptr) + { + fclose(s_log_file); + s_log_file = nullptr; + } + fopen_s(&s_log_file, filename, "w"); + + if (buffered_output.size() > 0) + { + for (const std::string& str : buffered_output) + { + global_logf(str.c_str()); + } + buffered_output.clear(); + } + } + + void log_base(const char* header, const char* message) + { + constexpr size_t final_timestamp_len = 32; + char final_timestamp[final_timestamp_len] = { }; + if (string_helpers::create_timestamp_str(final_timestamp, final_timestamp_len)) + { + global_logf("%s NativePlugin (%s): %s", final_timestamp, header, message); + } + else + { + global_logf("NativePlugin (%s): %s", header, message); + } + } + + //------------------------------------------------------------------------- + // TODO: If possible, hook this up into a proper logging channel.s + void log_warn(const char* log_string) + { +#if SHOW_DIALOG_BOX_ON_WARN + show_log_as_dialog(log_string); +#endif + log_base("WARNING", log_string); + } + + void log_inform(const char* log_string) + { + log_base("INFORM", log_string); + } + + void log_error(const char* log_string) + { + log_base("ERROR", log_string); + } +} diff --git a/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/string_helpers.cpp b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/string_helpers.cpp new file mode 100644 index 000000000..d24d6180d --- /dev/null +++ b/lib/NativeCode/DynamicLibraryLoaderHelper/NativeRender/src/string_helpers.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 PlayEveryWare + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "string_helpers.h" + +namespace pew::eos::string_helpers +{ + std::string trim(const std::string& str) + { + const auto start = std::find_if_not(str.begin(), str.end(), ::isspace); + const auto end = std::find_if_not(str.rbegin(), str.rend(), ::isspace).base(); + + if (start < end) + { + return std::basic_string(start, end); + } + else + { + return ""; + } + } + + std::vector split_and_trim(const std::string& input, char delimiter) + { + std::vector result; + std::stringstream ss(input); + std::string item; + + while (std::getline(ss, item, delimiter)) + { + std::string trimmedItem = trim(item); + if (!trimmedItem.empty()) + { + result.push_back(trimmedItem); + } + } + + return result; + } + + bool create_timestamp_str(char* final_timestamp, size_t final_timestamp_len) + { + constexpr size_t buffer_len = 32; + char buffer[buffer_len]; + + if (buffer_len > final_timestamp_len) + { + return false; + } + + time_t raw_time = time(nullptr); + tm time_info = { 0 }; + + timespec time_spec = { 0 }; + timespec_get(&time_spec, TIME_UTC); + localtime_s(&time_info, &raw_time); + + strftime(buffer, buffer_len, "%Y-%m-%dT%H:%M:%S", &time_info); + long milliseconds = (long)round(time_spec.tv_nsec / 1.0e6); + snprintf(final_timestamp, final_timestamp_len, "%s.%03ld", buffer, milliseconds); + + return true; + } + + size_t utf8_str_bytes_required_for_wide_str(const wchar_t* wide_str, int wide_str_len) + { + int bytes_required = WideCharToMultiByte(CP_UTF8, 0, wide_str, wide_str_len, NULL, 0, NULL, NULL); + + if (bytes_required < 0) + { + return 0; + } + + return bytes_required; + } + + // wide_str must be null terminated if wide_str_len is passed + bool copy_to_utf8_str_from_wide_str(char* RESTRICT utf8_str, size_t utf8_str_len, const wchar_t* RESTRICT wide_str, int wide_str_len) + { + if (utf8_str_len > INT_MAX) + { + return false; + } + + WideCharToMultiByte(CP_UTF8, 0, wide_str, wide_str_len, utf8_str, (int)utf8_str_len, NULL, NULL); + + return true; + } + + char* create_utf8_str_from_wide_str(const wchar_t* wide_str) + { + const int wide_str_len = (int)wcslen(wide_str) + 1; + int bytes_required = (int)utf8_str_bytes_required_for_wide_str(wide_str, wide_str_len); + char* to_return = (char*)malloc(bytes_required); + + if (!copy_to_utf8_str_from_wide_str(to_return, bytes_required, wide_str, wide_str_len)) + { + free(to_return); + to_return = NULL; + } + + return to_return; + } + + wchar_t* create_wide_str_from_utf8_str(const char* utf8_str) + { + int chars_required = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0); + wchar_t* to_return = (wchar_t*)malloc(chars_required * sizeof(wchar_t)); + int utf8_str_len = (int)strlen(utf8_str); + + MultiByteToWideChar(CP_UTF8, 0, utf8_str, utf8_str_len, to_return, chars_required); + + return to_return; + } + + std::string to_utf8_str(const std::wstring& wide_str) + { + std::wstring_convert> converter; + std::string utf8_str = converter.to_bytes(wide_str); + + return utf8_str; + } + + std::string to_utf8_str(const std::filesystem::path& path) + { + return to_utf8_str(path.native()); + } +} diff --git a/tools/coverage/results/Report/badge_linecoverage.svg b/tools/coverage/results/Report/badge_linecoverage.svg index 1cc7c4e27..74e6557eb 100644 --- a/tools/coverage/results/Report/badge_linecoverage.svg +++ b/tools/coverage/results/Report/badge_linecoverage.svg @@ -77,7 +77,7 @@ Coverage Coverage - 9.5%9.5% + 22.7%22.7% diff --git a/tools/coverage/results/current_coverage.txt b/tools/coverage/results/current_coverage.txt index e90f0d9ba..e837fe1a6 100644 --- a/tools/coverage/results/current_coverage.txt +++ b/tools/coverage/results/current_coverage.txt @@ -1 +1 @@ -10% \ No newline at end of file +23% \ No newline at end of file diff --git a/tools/scripts/check_links.py b/tools/scripts/check_links.py new file mode 100644 index 000000000..92e1ca001 --- /dev/null +++ b/tools/scripts/check_links.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import logging +import requests +import argparse +import json +from tqdm import tqdm +from urllib.parse import urlparse +from pathlib import Path +from concurrent.futures import ThreadPoolExecutor, as_completed +from bs4 import BeautifulSoup # Import BeautifulSoup + +# Set up logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Update this array with files that can be skipped for inspection +bypass_markdown_files = ( + # This is skipped because it is actually a symbolic link + './.github/README.md' +) + +# Update this array with links that are acceptable to fail inspection +bypass_inspection_links = ( + 'http://localhost:8080', + 'https://eoshelp.epicgames.com/' +) + +# Cache for file encoding detection (optional, for handling encoding issues) +file_encoding_cache = {} + +def find_markdown_files(root_dir): + """ + Recursively find all Markdown files in the given directory. + + Args: + root_dir (str): The root directory to search for Markdown files. + + Returns: + list: A list of paths to Markdown files, sorted in descending order by file size. + """ + md_files = [] + for dirpath, dirnames, filenames in os.walk(root_dir): + for filename in filenames: + # Skip if the file does not have an '.md' extension + if not filename.lower().endswith('.md'): + continue + + filepath = os.path.join(dirpath, filename) + + # Skip if the file is a symbolic link + if os.path.islink(filepath): + continue + + # Skip if the file is in the list of files to bypass inspection + if filepath in bypass_markdown_files: + continue + + # Otherwise, add it to the list of markdown files to consider. + md_files.append(os.path.join(dirpath, filename)) + + # Sort the files in descending order of size + md_files.sort(key=lambda x: os.path.getsize(x), reverse=True) + + return md_files + +def extract_links_from_markdown(md_file): + """ + Extract all links and image sources from a Markdown file, including those in HTML tags. + + Args: + md_file (str): The path to the Markdown file. + + Returns: + list: A list of extracted links and image sources found in the Markdown file. + """ + try: + encoding = file_encoding_cache.get(md_file, 'utf-8') + with open(md_file, 'r', encoding=encoding, errors='ignore') as f: + content = f.read() + except Exception as e: + logging.warning(f"Error reading file {md_file}: {e}") + return [] + + # Regex patterns to find Markdown links and images + link_pattern = re.compile(r'(? tags + html_links = [a.get('href') for a in soup.find_all('a', href=True)] + + # Extract src attributes from tags + html_images = [img.get('src') for img in soup.find_all('img', src=True)] + + # Combine all links + all_links = links + images + html_links + html_images + + # Filter out None values and duplicates + all_links = list(set(filter(None, all_links))) + + return all_links + +def is_external_link(url): + """ + Determine if a URL is an external link. + + Args: + url (str): The URL to check. + + Returns: + bool: True if the URL is external (has http, https, or ftp scheme), False otherwise. + """ + parsed_url = urlparse(url) + return parsed_url.scheme in ('http', 'https', 'ftp') + +def get_md_files_to_links(md_files, include_external): + """ + Create a dictionary mapping Markdown files to their extracted links. + + Args: + md_files (list): A list of Markdown file paths. + include_external (bool): Whether to include external links in the extraction. + + Returns: + dict: A dictionary where keys are Markdown file paths and values are lists of links to inspect. + """ + md_files_to_links = {} + for md_file in md_files: + links_in_md_file = extract_links_from_markdown(md_file) + links_to_inspect = [] + md_dir = os.path.dirname(md_file) + + for link in links_in_md_file: + link = link.strip() + + if link in bypass_inspection_links: + continue + if not link or link.startswith('#'): + continue + if link.startswith(('mailto:', 'tel:')): + continue + if is_external_link(link) and not include_external: + continue + + links_to_inspect.append(link) + + links_to_inspect = list(set(links_to_inspect)) + md_files_to_links[md_file] = links_to_inspect + + return md_files_to_links + +def compute_abs_link(md_dir, link): + """ + Compute the absolute path of a link, handling anchors and normalizing the path. + + Args: + md_dir (str): The directory of the Markdown file containing the link. + link (str): The link to compute the absolute path for. + + Returns: + str: The normalized absolute path of the link. + """ + link_path = link.split('#')[0] + abs_path = os.path.normpath(os.path.join(md_dir, link_path)) + abs_path = abs_path.lstrip("/\\") + return abs_path + +def check_link(session, link, md_file, headers, link_inspection_results): + """ + Inspect a single link to determine if it is valid. + + Args: + session (requests.Session): The requests session to use for HTTP requests. + link (str): The link to inspect. + md_file (str): The Markdown file containing the link. + headers (dict): HTTP headers to use in requests. + link_inspection_results (dict): A cache of previously inspected links. + + Returns: + tuple: A tuple containing the inspection result (None if valid, error message if broken), + the absolute link path, the Markdown file path, and the original link. + """ + md_dir = os.path.dirname(md_file) + result = None + abs_link = link + + # Cache key includes md_dir for internal links to avoid conflicts + cache_key = link if is_external_link(link) else os.path.normpath(os.path.join(md_dir, link)) + + # Check if the link has already been inspected + if cache_key in link_inspection_results: + result = link_inspection_results[cache_key] + if not is_external_link(link): + abs_link = compute_abs_link(md_dir, link) + else: + if is_external_link(link): + try: + # Try to get the response using the HEAD method + response = session.head(link, allow_redirects=True, timeout=10, headers=headers) + + # If the HEAD method is not allowed, use GET + if response.status_code == 405: + response = session.get(link, allow_redirects=True, timeout=10, headers=headers) + + # Continue inspection if status code is not 403 but is >= 400. + # The reason to ignore status_code 403 is because some of the links in + # the markdown files point to documentation pages within + # dev.epicgames.com that are only accessible when logged in. + if response.status_code != 403 and response.status_code >= 400: + result = response.status_code + + except requests.exceptions.RequestException as e: + result = str(e) + else: + abs_link = compute_abs_link(md_dir, link) + if not os.path.exists(abs_link) and not os.path.exists(link): + result = 'File not found' + + link_inspection_results[cache_key] = result + + return result, abs_link, md_file, link + +def check_links(md_files, include_external=False, concurrent_requests=10): + """ + Check all links in the given Markdown files and return a list of broken links. + + Args: + md_files (list): A list of Markdown file paths. + include_external (bool, optional): Whether to include external links in the check. Defaults to False. + concurrent_requests (int, optional): Number of concurrent requests for external links. Defaults to 10. + + Returns: + tuple: A tuple containing: + - broken_links (list): A list of tuples (md_file, link, abs_link, error) for broken links. + - links_inspected_count (int): Total number of links inspected. + """ + broken_links = [] + session = requests.Session() + headers = {'User-Agent': 'Mozilla/5.0 (LinkChecker/1.0)'} + + md_files_to_links = get_md_files_to_links(md_files, include_external) + + link_inspection_results = {} + links_to_inspect = sum(len(links) for links in md_files_to_links.values()) + links_inspected_count = 0 + + def generate_items(): + for md_file, link_list in md_files_to_links.items(): + for link in link_list: + yield md_file, link + + with tqdm(total=links_to_inspect, desc="Inspecting links") as pbar: + with ThreadPoolExecutor(max_workers=concurrent_requests) as executor: + futures = [] + for md_file, link in generate_items(): + futures.append(executor.submit(check_link, session, link, md_file, headers, link_inspection_results)) + + for future in as_completed(futures): + result, abs_link, md_file, link = future.result() + if result is not None: + broken_links.append((md_file, link, abs_link, result)) + links_inspected_count += 1 + pbar.set_postfix(broken=len(broken_links)) + pbar.update(1) + + return broken_links, links_inspected_count + +def main(): + """ + The main function to execute the link checker script. + Parses arguments, finds Markdown files, checks links, and outputs results. + """ + parser = argparse.ArgumentParser(description="Link checker for Markdown files.") + parser.add_argument("--root-dir", type=str, default=".", help="Root directory to search for Markdown files") + parser.add_argument("--include-external", action="store_true", help="Include external links in the inspection") + parser.add_argument("--concurrent-requests", type=int, default=10, help="Number of concurrent requests for external links") + parser.add_argument("--output-format", type=str, choices=["json", "text"], default="text", help="Output format (json or text)") + + args = parser.parse_args() + + root_dir = args.root_dir + md_files = find_markdown_files(root_dir) + broken_links, links_inspected = check_links(md_files, include_external=args.include_external, concurrent_requests=args.concurrent_requests) + + if broken_links: + if args.output_format == "json": + output = { + "broken_links": [ + { + "file": md_file, + "link": link, + "absolute_link": abs_link, + "error": error + } for md_file, link, abs_link, error in broken_links + ], + "total_links_inspected": links_inspected + } + print(json.dumps(output, indent=2)) + else: + print("Broken links found:") + for md_file, link, abs_link, error in broken_links: + print(f"In file: {md_file}") + print(f" - Link: {link}") + if link != abs_link: + print(f" - Absolute Link: {abs_link}") + print(f" - Error: {error}") + print(f"A total of {len(broken_links)} broken links were identified out of {links_inspected} inspected.") + sys.exit(1) + else: + print(f"No broken links found. A total of {links_inspected} links were inspected.") + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/tools/scripts/print-commits.sh b/tools/scripts/print-commits.sh new file mode 100644 index 000000000..0549a4951 --- /dev/null +++ b/tools/scripts/print-commits.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Get the current branch name +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + +# Get the most recent tag by commit history (linear, not just nearest) +MOST_RECENT_TAG=$(git tag --sort=-creatordate | head -n 1) + +# Check if a tag was found +if [ -z "$MOST_RECENT_TAG" ]; then + echo "No tags found in the repository." + exit 1 +fi + +# Print commit messages, hashes, and timestamps between the most recent tag and the current branch +echo "Commit messages between branch '$CURRENT_BRANCH' and the most recent tag '$MOST_RECENT_TAG':" +git log --pretty=format:"%cd - %an - %h - %s" --date=short $MOST_RECENT_TAG..$CURRENT_BRANCH \ No newline at end of file