From 3cd18ff6133cd07c87ae2085de8a0daa22934b2c Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Sun, 27 Apr 2025 13:26:16 +0200 Subject: [PATCH 01/13] Refactor: Replace PhoneApp.InitApp coroutine with HomeScreen.Start injection --- S1API/Internal/Patches/HomeScreen.Start.cs | 53 ++++++ S1API/Internal/Patches/PhoneAppRegistry.cs | 36 ++++ S1API/PhoneApp/PhoneApp.cs | 201 ++++++++++++--------- 3 files changed, 202 insertions(+), 88 deletions(-) create mode 100644 S1API/Internal/Patches/HomeScreen.Start.cs create mode 100644 S1API/Internal/Patches/PhoneAppRegistry.cs diff --git a/S1API/Internal/Patches/HomeScreen.Start.cs b/S1API/Internal/Patches/HomeScreen.Start.cs new file mode 100644 index 00000000..ab8ab830 --- /dev/null +++ b/S1API/Internal/Patches/HomeScreen.Start.cs @@ -0,0 +1,53 @@ +using System; +using HarmonyLib; +using Il2CppScheduleOne.UI.Phone; +using UnityEngine.SceneManagement; +using S1API.Internal.Utils; +using S1API.Internal.Abstraction; +using S1API.PhoneApp; + +namespace S1API.Internal.Patches; + +/// +/// The HomeScreen_Start class contains functionality that patches the +/// HomeScreen's Start method using the Harmony library within the Il2CppScheduleOne UI.Phone namespace. +/// This class is part of the S1API.Internal.Patches namespace, enabling modification or extension +/// of the behavior of the HomeScreen component's Start method. +/// +public class HomeScreen_Start +{ + /// + /// Represents a patch class for modifying the behavior of the Start method in the HomeScreen class. + /// This class is implemented as part of the Harmony patching mechanism to apply modifications + /// or inject additional logic during the execution of the Start method. + /// + [HarmonyPatch(typeof(HomeScreen), "Start")] + internal static class HomeScreen_Start_Patch + { + /// + /// Postfix method to modify the behavior of the HomeScreen's Start method after it is executed. + /// + /// + /// This method iterates over the list of registered phone applications and performs initialization for each app. + /// It ensures that the UI panel and the app icon for each application are created and properly configured. + /// If an error occurs during this process, a warning is logged with relevant details. + /// + static void Postfix() + { + foreach (var app in PhoneAppRegistry.RegisteredApps) + { + try + { + app.SpawnUI(); // Clone ProductManagerApp, clear container, call OnCreatedUI + app.SpawnIcon(); // Clone last HomeScreen icon, set label + icon image + } + catch (Exception e) + { + MelonLoader.MelonLogger.Warning($"[PhoneApp] Failed to spawn UI for {app.GetType().Name}: {e.Message}"); + } + } + } + } + + +} \ No newline at end of file diff --git a/S1API/Internal/Patches/PhoneAppRegistry.cs b/S1API/Internal/Patches/PhoneAppRegistry.cs new file mode 100644 index 00000000..996162da --- /dev/null +++ b/S1API/Internal/Patches/PhoneAppRegistry.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace S1API.PhoneApp +{ + /// + /// Provides functionality for managing the registration of custom phone applications. + /// + /// + /// This static class serves as a registry for tracking instances of phone applications. + /// Applications are added to a centralized list which can then be used for initialization or + /// interacting with registered applications at runtime. + /// + internal static class PhoneAppRegistry + { + /// + /// A static readonly list that stores instances of phone applications registered via the PhoneAppRegistry. + /// + /// + /// This list holds all registered instances of objects. Applications are added to this collection + /// whenever they are registered using the PhoneAppRegistry.Register method, which is typically called automatically + /// during the application's lifecycle. + /// It serves as a central repository for all in-game phone applications, enabling other systems to access and manage + /// these registered apps efficiently. + /// + public static readonly List RegisteredApps = new(); + + /// + /// Registers a specified phone app into the phone application registry. + /// + /// The PhoneApp instance to be registered. + public static void Register(PhoneApp app) + { + RegisteredApps.Add(app); + } + } +} \ No newline at end of file diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index 7f59e4e7..0b9033fd 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -1,8 +1,7 @@ -using System.Collections; -using System.IO; -using MelonLoader; +using System.IO; using UnityEngine; using UnityEngine.UI; +using MelonLoader; using Object = UnityEngine.Object; using MelonLoader.Utils; using S1API.Internal.Abstraction; @@ -10,77 +9,110 @@ namespace S1API.PhoneApp { /// - /// Serves as an abstract base class for creating in-game phone applications with customizable functionality and appearance. + /// Abstract base class for creating custom applications to be used within an in-game phone system. /// /// - /// Implementations of this class enable the creation of bespoke applications that integrate seamlessly into a game's phone system. - /// Derived classes are required to define key properties and methods, such as application name, icon, and UI behavior. - /// This class also manages the app's lifecycle, including its initialization and destruction processes. + /// This class provides an extensible framework for defining application behaviors, user interface elements, + /// and registration mechanics for integration into the phone's ecosystem. /// public abstract class PhoneApp : Registerable { /// - /// INTERNAL: A dedicated logger for all custom phone apps. + /// Logger instance used for logging messages, warnings, or errors + /// related to the functionality of in-game phone applications. /// protected static readonly MelonLogger.Instance LoggerInstance = new MelonLogger.Instance("PhoneApp"); /// - /// The player object in the scene. - /// - private GameObject? _player; - - /// - /// The in-game UI panel representing the app. + /// Represents the panel associated with the phone app's UI. + /// This is dynamically instantiated or retrieved when the app is initiated and serves as the container + /// for the app's user interface elements within the phone system. The panel exists within the + /// app canvas structure in the game's Unity hierarchy. /// private GameObject? _appPanel; - // TODO (@omar-akermi): Can you look into if this is still needed pls? /// - /// Whether the app was successfully created. + /// Indicates whether the application UI has been successfully created and initialized. /// + /// + /// This variable is used internally to track the state of the application's UI. + /// When set to true, it denotes that the app UI panel has been created and configured. + /// private bool _appCreated; /// - /// Whether the app icon was already modified. + /// Indicates whether the phone application icon has been modified. + /// This flag prevents redundant modification of the icon once it has already + /// been updated or created. /// private bool _iconModified; /// - /// Unique GameObject name of the app (e.g. "SilkroadApp"). + /// Gets the unique identifier for the application within the phone system. /// + /// + /// This property is used as a key to identify the application when creating UI elements or interacting with other components + /// of the in-game phone system. It must be implemented in derived classes to provide a consistent and unique name for + /// the application. + /// protected abstract string AppName { get; } /// - /// The title shown at the top of the app UI. + /// Gets the display title of the application as it appears in the in-game phone system. /// + /// + /// This property specifies the human-readable name of the application, different from the internal + /// AppName that uniquely identifies the app within the system. It is displayed to the user + /// on the application icon or within the application UI. + /// protected abstract string AppTitle { get; } - + /// - /// The label shown under the app icon on the home screen. + /// Gets the label text displayed on the application's icon. /// + /// + /// The IconLabel property is an abstract member that must be overridden by each implementation + /// of the class. It specifies the label text shown directly below the application's + /// icon on the in-game phone's home screen. + /// This property is utilized when creating or modifying the app's icon, as part of the SpawnIcon method, + /// to ensure that the label represents the application's name or a relevant description. The value should + /// be concise and contextually meaningful to the user. + /// + /// + /// A string representing the label text displayed under the app icon, which explains or identifies + /// the app to the user. + /// protected abstract string IconLabel { get; } /// - /// The PNG file name (in UserData) used for the app icon. + /// Specifies the file name of the icon used to represent the phone application in the in-game phone system. /// + /// + /// The value of this property is typically a string containing the file name of the icon asset, + /// such as "icon-name.png". It is used to identify and load the appropriate icon for the application. + /// protected abstract string IconFileName { get; } /// - /// Called when the app's UI should be created inside the container. + /// Invoked to define the user interface layout when the application panel is created. + /// The method is used to populate the provided container with custom UI elements specific to the application. /// - /// The container GameObject to build into. + /// The GameObject container where the application's UI elements will be added. protected abstract void OnCreatedUI(GameObject container); /// - /// Called when the app is loaded into the scene (delayed after phone UI is present). + /// Invoked when the PhoneApp instance is created. + /// Responsible for registering the app with the PhoneAppRegistry, + /// integrating it into the in-game phone system. /// protected override void OnCreated() { - MelonCoroutines.Start(InitApp()); + PhoneAppRegistry.Register(this); } /// - /// Called when the app is unloaded or the scene is reset. + /// Cleans up resources and resets state when the app is destroyed. + /// This method ensures any associated UI elements and resources are properly disposed of and variables tracking the app state are reset. /// protected override void OnDestroyed() { @@ -95,24 +127,19 @@ protected override void OnDestroyed() } /// - /// Coroutine that injects the app UI and icon after scene/UI has loaded. + /// Generates and initializes the UI panel for the application within the in-game phone system. + /// This method locates the parent container in the UI hierarchy, clones a template panel if needed, + /// clears its content, and then invokes the implementation-specific OnCreatedUI method + /// for further customization of the UI panel. /// - private IEnumerator InitApp() + internal void SpawnUI() { - yield return new WaitForSeconds(5f); - - _player = GameObject.Find("Player_Local"); - if (_player == null) - { - LoggerInstance.Error("Player_Local not found."); - yield break; - } - - GameObject appsCanvas = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/AppsCanvas"); + GameObject appsCanvas = + GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/AppsCanvas"); if (appsCanvas == null) { LoggerInstance.Error("AppsCanvas not found."); - yield break; + return; } Transform existingApp = appsCanvas.transform.Find(AppName); @@ -127,7 +154,7 @@ private IEnumerator InitApp() if (templateApp == null) { LoggerInstance.Error("Template ProductManagerApp not found."); - yield break; + return; } _appPanel = Object.Instantiate(templateApp.gameObject, appsCanvas.transform); @@ -145,17 +172,46 @@ private IEnumerator InitApp() } _appPanel.SetActive(true); + } + + /// + /// Creates or modifies the application icon displayed on the in-game phone's home screen. + /// This method clones an existing icon, updates its label, and changes its image based on the provided file name. + /// + internal void SpawnIcon() + { + if (_iconModified) + return; - if (!_iconModified) + GameObject parent = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/HomeScreen/AppIcons/"); + if (parent == null) + { + LoggerInstance.Error("AppIcons not found."); + return; + } + + Transform? lastIcon = parent.transform.childCount > 0 ? parent.transform.GetChild(parent.transform.childCount - 1) : null; + if (lastIcon == null) { - _iconModified = ModifyAppIcon(IconLabel, IconFileName); + LoggerInstance.Error("No icon found to clone."); + return; } + + GameObject iconObj = lastIcon.gameObject; + iconObj.name = AppName; + + Transform labelTransform = iconObj.transform.Find("Label"); + Text? label = labelTransform?.GetComponent(); + if (label != null) + label.text = IconLabel; + + _iconModified = ChangeAppIconImage(iconObj, IconFileName); } /// - /// Configures the provided GameObject panel to prepare it for use with the app. + /// Configures an existing app panel by clearing and rebuilding its UI elements if necessary. /// - /// The GameObject representing the UI panel of the app. + /// The app panel to configure, represented as a GameObject. private void SetupExistingAppPanel(GameObject panel) { Transform containerTransform = panel.transform.Find("Container"); @@ -172,6 +228,10 @@ private void SetupExistingAppPanel(GameObject panel) _appCreated = true; } + /// + /// Removes all child objects from the specified container to clear its contents. + /// + /// The parent GameObject whose child objects will be destroyed. private void ClearContainer(GameObject container) { for (int i = container.transform.childCount - 1; i >= 0; i--) @@ -179,63 +239,28 @@ private void ClearContainer(GameObject container) } /// - /// Modifies the application's icon by cloning an existing icon, updating its label, - /// and setting a new icon image based on the specified parameters. + /// Changes the image of the app icon based on the specified filename, and applies the new icon to the given GameObject. /// - /// The text to be displayed as the label for the modified icon. - /// The file name of the new icon image to apply. + /// The GameObject representing the app icon that will have its image changed. + /// The name of the file containing the new icon image to be loaded. /// - /// A boolean value indicating whether the icon modification was successful. - /// Returns true if the modification was completed successfully; otherwise, false. + /// A boolean value indicating whether the operation was successful. + /// Returns true if the image was successfully loaded and applied; otherwise, returns false. /// - private bool ModifyAppIcon(string labelText, string fileName) - { - GameObject parent = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/HomeScreen/AppIcons/"); - if (parent == null) - { - LoggerInstance?.Error("AppIcons not found."); - return false; - } - - Transform? lastIcon = parent.transform.childCount > 0 ? parent.transform.GetChild(parent.transform.childCount - 1) : null; - if (lastIcon == null) - { - LoggerInstance?.Error("No icon found to clone."); - return false; - } - - GameObject iconObj = lastIcon.gameObject; - iconObj.name = AppName; - - Transform labelTransform = iconObj.transform.Find("Label"); - Text? label = labelTransform?.GetComponent(); - if (label != null) - label.text = labelText; - - return ChangeAppIconImage(iconObj, fileName); - } - - - /// - /// Updates the app icon image with the specified file if the corresponding Image component is found and the file exists. - /// - /// The GameObject representing the app icon whose image is to be updated. - /// The name of the image file to be loaded and applied as the icon. - /// True if the icon image was successfully updated, otherwise false. private bool ChangeAppIconImage(GameObject iconObj, string filename) { Transform imageTransform = iconObj.transform.Find("Mask/Image"); Image? image = imageTransform?.GetComponent(); if (image == null) { - LoggerInstance?.Error("Image component not found in icon."); + LoggerInstance.Error("Image component not found in icon."); return false; } string path = Path.Combine(MelonEnvironment.ModsDirectory, filename); if (!File.Exists(path)) { - LoggerInstance?.Error("Icon file not found: " + path); + LoggerInstance.Error("Icon file not found: " + path); return false; } @@ -252,7 +277,7 @@ private bool ChangeAppIconImage(GameObject iconObj, string filename) } catch (System.Exception e) { - LoggerInstance?.Error("Failed to load image: " + e.Message); + LoggerInstance.Error("Failed to load image: " + e.Message); } return false; From abdb0f54b0e5a3492b6261278e46ebfe16613225 Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Sun, 27 Apr 2025 14:25:06 +0200 Subject: [PATCH 02/13] refactor: dynamic finding --- S1API/Internal/Patches/HomeScreen.Start.cs | 56 ++++++++++------------ S1API/Internal/Patches/PhoneAppPatches.cs | 20 ++++---- S1API/Internal/Patches/PhoneAppRegistry.cs | 6 +-- S1API/PhoneApp/PhoneApp.cs | 42 +++++++++------- 4 files changed, 63 insertions(+), 61 deletions(-) diff --git a/S1API/Internal/Patches/HomeScreen.Start.cs b/S1API/Internal/Patches/HomeScreen.Start.cs index ab8ab830..59930b41 100644 --- a/S1API/Internal/Patches/HomeScreen.Start.cs +++ b/S1API/Internal/Patches/HomeScreen.Start.cs @@ -1,53 +1,45 @@ using System; using HarmonyLib; -using Il2CppScheduleOne.UI.Phone; using UnityEngine.SceneManagement; using S1API.Internal.Utils; using S1API.Internal.Abstraction; using S1API.PhoneApp; +#if (IL2CPPMELON || IL2CPPBEPINEX) +using Il2CppScheduleOne.UI.Phone; +#else +using ScheduleOne.UI.Phone; +#endif -namespace S1API.Internal.Patches; - -/// -/// The HomeScreen_Start class contains functionality that patches the -/// HomeScreen's Start method using the Harmony library within the Il2CppScheduleOne UI.Phone namespace. -/// This class is part of the S1API.Internal.Patches namespace, enabling modification or extension -/// of the behavior of the HomeScreen component's Start method. -/// -public class HomeScreen_Start +namespace S1API.Internal.Patches { /// - /// Represents a patch class for modifying the behavior of the Start method in the HomeScreen class. - /// This class is implemented as part of the Harmony patching mechanism to apply modifications - /// or inject additional logic during the execution of the Start method. + /// The HomeScreen_Start class contains functionality that patches the + /// HomeScreen's Start method using the Harmony library within the Il2CppScheduleOne UI.Phone namespace. + /// This class is part of the S1API.Internal.Patches namespace, enabling modification or extension + /// of the behavior of the HomeScreen component's Start method. /// - [HarmonyPatch(typeof(HomeScreen), "Start")] - internal static class HomeScreen_Start_Patch + public class HomeScreen_Start { /// - /// Postfix method to modify the behavior of the HomeScreen's Start method after it is executed. + /// Represents a patch class for modifying the behavior of the Start method in the HomeScreen class. + /// This class is implemented as part of the Harmony patching mechanism to apply modifications + /// or inject additional logic during the execution of the Start method. /// - /// - /// This method iterates over the list of registered phone applications and performs initialization for each app. - /// It ensures that the UI panel and the app icon for each application are created and properly configured. - /// If an error occurs during this process, a warning is logged with relevant details. - /// - static void Postfix() + [HarmonyPatch(typeof(HomeScreen), "Start")] + internal static class HomeScreen_Start_Patch { - foreach (var app in PhoneAppRegistry.RegisteredApps) + static void Postfix(HomeScreen __instance) { - try - { - app.SpawnUI(); // Clone ProductManagerApp, clear container, call OnCreatedUI - app.SpawnIcon(); // Clone last HomeScreen icon, set label + icon image - } - catch (Exception e) + if (__instance == null) + return; + + foreach (var app in PhoneAppRegistry.RegisteredApps) { - MelonLoader.MelonLogger.Warning($"[PhoneApp] Failed to spawn UI for {app.GetType().Name}: {e.Message}"); + app.SpawnUI(__instance); + app.SpawnIcon(__instance); } } } - } - + } } \ No newline at end of file diff --git a/S1API/Internal/Patches/PhoneAppPatches.cs b/S1API/Internal/Patches/PhoneAppPatches.cs index 0229638b..b13f3049 100644 --- a/S1API/Internal/Patches/PhoneAppPatches.cs +++ b/S1API/Internal/Patches/PhoneAppPatches.cs @@ -3,6 +3,7 @@ using UnityEngine.SceneManagement; using S1API.Internal.Utils; using S1API.Internal.Abstraction; +using S1API.Logging; using S1API.PhoneApp; namespace S1API.Internal.Patches @@ -15,8 +16,7 @@ namespace S1API.Internal.Patches #endif internal static class PhoneAppPatches { - // TODO (@omar-akermi): Can you look into if this is still needed pls? - private static bool _loaded = false; + private static readonly Log Logger = new Log("PhoneApp"); /// /// Executes logic after the Unity SceneManager completes loading a scene. @@ -27,11 +27,12 @@ internal static class PhoneAppPatches /// The loading mode used by the SceneManager. static void Postfix(Scene scene, LoadSceneMode mode) { - if (scene.name != "Main") return; - - // Re-register all PhoneApps every time the Main scene loads - var phoneApp = ReflectionUtils.GetDerivedClasses(); - foreach (var type in phoneApp) + if (scene.name != "Main") + { + return; + } + var phoneApps = ReflectionUtils.GetDerivedClasses(); + foreach (var type in phoneApps) { if (type.GetConstructor(Type.EmptyTypes) == null) continue; @@ -40,11 +41,12 @@ static void Postfix(Scene scene, LoadSceneMode mode) var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!; ((IRegisterable)instance).CreateInternal(); } - catch (System.Exception e) + catch (Exception e) { - MelonLoader.MelonLogger.Warning($"[PhoneApp] Failed to register {type.FullName}: {e.Message}"); + Logger.Error($"Failed to create instance of {type.Name}: {e}"); } } } + } } \ No newline at end of file diff --git a/S1API/Internal/Patches/PhoneAppRegistry.cs b/S1API/Internal/Patches/PhoneAppRegistry.cs index 996162da..af62c2b3 100644 --- a/S1API/Internal/Patches/PhoneAppRegistry.cs +++ b/S1API/Internal/Patches/PhoneAppRegistry.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace S1API.PhoneApp +namespace S1API.Internal.Patches { /// /// Provides functionality for managing the registration of custom phone applications. @@ -22,13 +22,13 @@ internal static class PhoneAppRegistry /// It serves as a central repository for all in-game phone applications, enabling other systems to access and manage /// these registered apps efficiently. /// - public static readonly List RegisteredApps = new(); + public static readonly List RegisteredApps = new List(); /// /// Registers a specified phone app into the phone application registry. /// /// The PhoneApp instance to be registered. - public static void Register(PhoneApp app) + public static void Register(PhoneApp.PhoneApp app) { RegisteredApps.Add(app); } diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index 0b9033fd..daffd049 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -5,7 +5,12 @@ using Object = UnityEngine.Object; using MelonLoader.Utils; using S1API.Internal.Abstraction; - +using S1API.Internal.Patches; +#if IL2CPPBEPINEX || IL2CPPMELON +using Il2CppScheduleOne.UI.Phone; +#else +using ScheduleOne.UI.Phone; +#endif namespace S1API.PhoneApp { /// @@ -21,7 +26,7 @@ public abstract class PhoneApp : Registerable /// Logger instance used for logging messages, warnings, or errors /// related to the functionality of in-game phone applications. /// - protected static readonly MelonLogger.Instance LoggerInstance = new MelonLogger.Instance("PhoneApp"); + protected static readonly Logging.Log Logger = new Logging.Log("PhoneApp"); /// /// Represents the panel associated with the phone app's UI. @@ -132,13 +137,12 @@ protected override void OnDestroyed() /// clears its content, and then invokes the implementation-specific OnCreatedUI method /// for further customization of the UI panel. /// - internal void SpawnUI() + internal void SpawnUI(HomeScreen homeScreenInstance) { - GameObject appsCanvas = - GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/AppsCanvas"); + GameObject? appsCanvas = homeScreenInstance.transform.parent.Find("AppsCanvas")?.gameObject; if (appsCanvas == null) { - LoggerInstance.Error("AppsCanvas not found."); + Logger.Error("AppsCanvas not found."); return; } @@ -153,7 +157,7 @@ internal void SpawnUI() Transform templateApp = appsCanvas.transform.Find("ProductManagerApp"); if (templateApp == null) { - LoggerInstance.Error("Template ProductManagerApp not found."); + Logger.Error("Template ProductManagerApp not found."); return; } @@ -178,36 +182,40 @@ internal void SpawnUI() /// Creates or modifies the application icon displayed on the in-game phone's home screen. /// This method clones an existing icon, updates its label, and changes its image based on the provided file name. /// - internal void SpawnIcon() + internal void SpawnIcon(HomeScreen homeScreenInstance) { if (_iconModified) return; - GameObject parent = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/HomeScreen/AppIcons/"); - if (parent == null) + GameObject? appIcons = homeScreenInstance.transform.Find("AppIcons")?.gameObject; + if (appIcons == null) { - LoggerInstance.Error("AppIcons not found."); + Logger.Error("AppIcons not found under HomeScreen."); return; } - Transform? lastIcon = parent.transform.childCount > 0 ? parent.transform.GetChild(parent.transform.childCount - 1) : null; + // Find the LAST icon (the one most recently added) + Transform? lastIcon = appIcons.transform.childCount > 0 ? appIcons.transform.GetChild(appIcons.transform.childCount - 1) : null; if (lastIcon == null) { - LoggerInstance.Error("No icon found to clone."); + Logger.Error("No icons found in AppIcons."); return; } GameObject iconObj = lastIcon.gameObject; - iconObj.name = AppName; + iconObj.name = AppName; // Rename it now + // Update label Transform labelTransform = iconObj.transform.Find("Label"); Text? label = labelTransform?.GetComponent(); if (label != null) label.text = IconLabel; + // Update image _iconModified = ChangeAppIconImage(iconObj, IconFileName); } + /// /// Configures an existing app panel by clearing and rebuilding its UI elements if necessary. /// @@ -253,14 +261,14 @@ private bool ChangeAppIconImage(GameObject iconObj, string filename) Image? image = imageTransform?.GetComponent(); if (image == null) { - LoggerInstance.Error("Image component not found in icon."); + Logger.Error("Image component not found in icon."); return false; } string path = Path.Combine(MelonEnvironment.ModsDirectory, filename); if (!File.Exists(path)) { - LoggerInstance.Error("Icon file not found: " + path); + Logger.Error("Icon file not found: " + path); return false; } @@ -277,7 +285,7 @@ private bool ChangeAppIconImage(GameObject iconObj, string filename) } catch (System.Exception e) { - LoggerInstance.Error("Failed to load image: " + e.Message); + Logger.Error("Failed to load image: " + e.Message); } return false; From 6ac6b813627eb67d472c63a19b528e18b25b16b3 Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Sun, 27 Apr 2025 14:39:58 +0200 Subject: [PATCH 03/13] move PhoneAppPatches.cs into HomeScreen.Start.cs --- S1API/Internal/Patches/HomeScreen.Start.cs | 49 ++++++++++++++++---- S1API/Internal/Patches/PhoneAppPatches.cs | 52 ---------------------- 2 files changed, 40 insertions(+), 61 deletions(-) delete mode 100644 S1API/Internal/Patches/PhoneAppPatches.cs diff --git a/S1API/Internal/Patches/HomeScreen.Start.cs b/S1API/Internal/Patches/HomeScreen.Start.cs index 59930b41..d779da14 100644 --- a/S1API/Internal/Patches/HomeScreen.Start.cs +++ b/S1API/Internal/Patches/HomeScreen.Start.cs @@ -4,6 +4,8 @@ using S1API.Internal.Utils; using S1API.Internal.Abstraction; using S1API.PhoneApp; +using S1API.Logging; + #if (IL2CPPMELON || IL2CPPBEPINEX) using Il2CppScheduleOne.UI.Phone; #else @@ -13,17 +15,47 @@ namespace S1API.Internal.Patches { /// - /// The HomeScreen_Start class contains functionality that patches the - /// HomeScreen's Start method using the Harmony library within the Il2CppScheduleOne UI.Phone namespace. - /// This class is part of the S1API.Internal.Patches namespace, enabling modification or extension - /// of the behavior of the HomeScreen component's Start method. + /// Patches related to PhoneApp system initialization and UI injection. /// public class HomeScreen_Start { + private static readonly Log Logger = new Log("PhoneApp"); + + /// + /// Patches SceneManager scene loading to register all PhoneApps after Main scene loads. + /// +#if (IL2CPPMELON || IL2CPPBEPINEX) + [HarmonyPatch(typeof(SceneManager), nameof(SceneManager.Internal_SceneLoaded))] +#else + [HarmonyPatch(typeof(SceneManager), "Internal_SceneLoaded", new Type[] { typeof(Scene), typeof(LoadSceneMode) })] +#endif + internal static class PhoneAppPatches + { + static void Postfix(Scene scene, LoadSceneMode mode) + { + if (scene.name != "Main") + return; + + var phoneApps = ReflectionUtils.GetDerivedClasses(); + foreach (var type in phoneApps) + { + if (type.GetConstructor(Type.EmptyTypes) == null) continue; + + try + { + var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!; + ((IRegisterable)instance).CreateInternal(); + } + catch (Exception e) + { + Logger.Error($"Failed to create instance of {type.Name}: {e}"); + } + } + } + } + /// - /// Represents a patch class for modifying the behavior of the Start method in the HomeScreen class. - /// This class is implemented as part of the Harmony patching mechanism to apply modifications - /// or inject additional logic during the execution of the Start method. + /// Patches HomeScreen.Start to spawn registered PhoneApp UIs and icons. /// [HarmonyPatch(typeof(HomeScreen), "Start")] internal static class HomeScreen_Start_Patch @@ -40,6 +72,5 @@ static void Postfix(HomeScreen __instance) } } } - } -} \ No newline at end of file +} diff --git a/S1API/Internal/Patches/PhoneAppPatches.cs b/S1API/Internal/Patches/PhoneAppPatches.cs deleted file mode 100644 index b13f3049..00000000 --- a/S1API/Internal/Patches/PhoneAppPatches.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using HarmonyLib; -using UnityEngine.SceneManagement; -using S1API.Internal.Utils; -using S1API.Internal.Abstraction; -using S1API.Logging; -using S1API.PhoneApp; - -namespace S1API.Internal.Patches -{ -#if (IL2CPPMELON || IL2CPPBEPINEX) - [HarmonyPatch(typeof(SceneManager), nameof(SceneManager.Internal_SceneLoaded))] -#else - [HarmonyPatch(typeof(SceneManager), "Internal_SceneLoaded", new Type[] { typeof(Scene), typeof(LoadSceneMode) })] - -#endif - internal static class PhoneAppPatches - { - private static readonly Log Logger = new Log("PhoneApp"); - - /// - /// Executes logic after the Unity SceneManager completes loading a scene. - /// Registers all derived implementations of the PhoneApp class during the loading - /// process of the "Main" scene. - /// - /// The scene that has been loaded. - /// The loading mode used by the SceneManager. - static void Postfix(Scene scene, LoadSceneMode mode) - { - if (scene.name != "Main") - { - return; - } - var phoneApps = ReflectionUtils.GetDerivedClasses(); - foreach (var type in phoneApps) - { - if (type.GetConstructor(Type.EmptyTypes) == null) continue; - - try - { - var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!; - ((IRegisterable)instance).CreateInternal(); - } - catch (Exception e) - { - Logger.Error($"Failed to create instance of {type.Name}: {e}"); - } - } - } - - } -} \ No newline at end of file From 5cfb6d896b6a68e63bc21c160049536e14c4f3cc Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Mon, 28 Apr 2025 00:21:14 +0200 Subject: [PATCH 04/13] fix references ? --- S1API/PhoneApp/PhoneApp.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index daffd049..a6aff653 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -1,15 +1,18 @@ using System.IO; using UnityEngine; using UnityEngine.UI; -using MelonLoader; using Object = UnityEngine.Object; -using MelonLoader.Utils; using S1API.Internal.Abstraction; using S1API.Internal.Patches; #if IL2CPPBEPINEX || IL2CPPMELON +using MelonLoader; +using MelonLoader.Utils; using Il2CppScheduleOne.UI.Phone; #else using ScheduleOne.UI.Phone; +using MelonLoader; +using MelonLoader.Utils; +using Il2CppScheduleOne.UI.Phone; #endif namespace S1API.PhoneApp { From c0fc1ad29fef708908254bc10e200ec246c5d77c Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Mon, 28 Apr 2025 00:37:49 +0200 Subject: [PATCH 05/13] pls work --- S1API/PhoneApp/PhoneApp.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index a6aff653..43212650 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -268,7 +268,11 @@ private bool ChangeAppIconImage(GameObject iconObj, string filename) return false; } +#if MONOMELON || IL2CPPMELON string path = Path.Combine(MelonEnvironment.ModsDirectory, filename); +#elif MONOBEPINEX || IL2CPPBEPINEX + string path = Path.Combine(BepInEx.Paths.PluginPath, filename); +#endif if (!File.Exists(path)) { Logger.Error("Icon file not found: " + path); From 63f41e3b00c7f82670ebe3566c8b61e7ce4bcfed Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Mon, 28 Apr 2025 00:39:38 +0200 Subject: [PATCH 06/13] last one --- S1API/PhoneApp/PhoneApp.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index 43212650..c618d924 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -4,15 +4,15 @@ using Object = UnityEngine.Object; using S1API.Internal.Abstraction; using S1API.Internal.Patches; -#if IL2CPPBEPINEX || IL2CPPMELON +#if (IL2CPPMELON || MONOMELON) using MelonLoader; using MelonLoader.Utils; +#endif + +#if (IL2CPPBEPINEX || IL2CPPMONO) using Il2CppScheduleOne.UI.Phone; -#else +#elif (MONOBEPINEX || MONOMELON) using ScheduleOne.UI.Phone; -using MelonLoader; -using MelonLoader.Utils; -using Il2CppScheduleOne.UI.Phone; #endif namespace S1API.PhoneApp { From e28a85c0a46396725c6a2cab114b714692725571 Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Mon, 28 Apr 2025 01:09:40 +0200 Subject: [PATCH 07/13] refactor homescreen --- S1API/Internal/Patches/HomeScreen.Start.cs | 71 +++++++++------------- S1API/PhoneApp/PhoneApp.cs | 14 +++-- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/S1API/Internal/Patches/HomeScreen.Start.cs b/S1API/Internal/Patches/HomeScreen.Start.cs index d779da14..be76234d 100644 --- a/S1API/Internal/Patches/HomeScreen.Start.cs +++ b/S1API/Internal/Patches/HomeScreen.Start.cs @@ -15,62 +15,47 @@ namespace S1API.Internal.Patches { /// - /// Patches related to PhoneApp system initialization and UI injection. + /// A Harmony patch for the Start method of the HomeScreen class, facilitating the registration and initialization of PhoneApps. /// - public class HomeScreen_Start + [HarmonyPatch(typeof(HomeScreen), "Start")] + internal static class HomeScreen_Start_Patch { + /// + /// A logging instance used for handling log messages pertaining to PhoneApp registration + /// and operations. Provides methods to log messages with different severity levels such + /// as Info, Warning, Error, and Fatal. + /// private static readonly Log Logger = new Log("PhoneApp"); /// - /// Patches SceneManager scene loading to register all PhoneApps after Main scene loads. + /// Executes after the HomeScreen's Start method to handle the registration + /// and initialization of PhoneApps. /// -#if (IL2CPPMELON || IL2CPPBEPINEX) - [HarmonyPatch(typeof(SceneManager), nameof(SceneManager.Internal_SceneLoaded))] -#else - [HarmonyPatch(typeof(SceneManager), "Internal_SceneLoaded", new Type[] { typeof(Scene), typeof(LoadSceneMode) })] -#endif - internal static class PhoneAppPatches + /// The HomeScreen instance being targeted in the patch. + static void Postfix(HomeScreen __instance) { - static void Postfix(Scene scene, LoadSceneMode mode) + if (__instance == null) + return; + + // Re-register all PhoneApps + var phoneApps = ReflectionUtils.GetDerivedClasses(); + foreach (var type in phoneApps) { - if (scene.name != "Main") - return; + if (type.GetConstructor(Type.EmptyTypes) == null) + continue; - var phoneApps = ReflectionUtils.GetDerivedClasses(); - foreach (var type in phoneApps) + try { - if (type.GetConstructor(Type.EmptyTypes) == null) continue; - - try - { - var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!; - ((IRegisterable)instance).CreateInternal(); - } - catch (Exception e) - { - Logger.Error($"Failed to create instance of {type.Name}: {e}"); - } + var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!; + ((IRegisterable)instance).CreateInternal(); + instance.SpawnUI(__instance); + instance.SpawnIcon(__instance); } - } - } - - /// - /// Patches HomeScreen.Start to spawn registered PhoneApp UIs and icons. - /// - [HarmonyPatch(typeof(HomeScreen), "Start")] - internal static class HomeScreen_Start_Patch - { - static void Postfix(HomeScreen __instance) - { - if (__instance == null) - return; - - foreach (var app in PhoneAppRegistry.RegisteredApps) + catch (Exception e) { - app.SpawnUI(__instance); - app.SpawnIcon(__instance); + Logger.Warning($"[PhoneApp] Failed to register {type.FullName}: {e.Message}"); } } } } -} +} \ No newline at end of file diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index c618d924..8bb0885f 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -1,18 +1,20 @@ using System.IO; +using Il2CppScheduleOne.UI.Phone; using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; using S1API.Internal.Abstraction; using S1API.Internal.Patches; -#if (IL2CPPMELON || MONOMELON) -using MelonLoader; +#if IL2CPPMELON +using Il2CppScheduleOne.UI.Phone; using MelonLoader.Utils; -#endif - -#if (IL2CPPBEPINEX || IL2CPPMONO) +#elif IL2CPPBEPINEX using Il2CppScheduleOne.UI.Phone; -#elif (MONOBEPINEX || MONOMELON) +#elif MONOBEPINEX +using ScheduleOne.UI.Phone; +#elif MONOMELON using ScheduleOne.UI.Phone; +using MelonLoader.Utils; #endif namespace S1API.PhoneApp { From f3ba4f2b297148eebf1b0cd8f9f0c459df7ae7b5 Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Mon, 28 Apr 2025 01:14:01 +0200 Subject: [PATCH 08/13] fix S1API.cs --- S1API/S1API.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/S1API/S1API.cs b/S1API/S1API.cs index ba4eaa7b..117a16d3 100644 --- a/S1API/S1API.cs +++ b/S1API/S1API.cs @@ -12,8 +12,9 @@ public class S1API : MelonMod { } } -#elif (MONOBEPINEX || IL2CPPBEPINEX) +#elif (IL2CPPBEPINEX) using BepInEx; +#elif MONOBEPINEX using BepInEx.Unity.Mono; using HarmonyLib; From b0800adf3a2872b4fdd09235145470576696bb4f Mon Sep 17 00:00:00 2001 From: Omar Akermi Date: Mon, 28 Apr 2025 01:20:19 +0200 Subject: [PATCH 09/13] should work --- S1API/PhoneApp/PhoneApp.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index 8bb0885f..f0b426fd 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -1,5 +1,4 @@ using System.IO; -using Il2CppScheduleOne.UI.Phone; using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; From 475b0fbdb074f061bc698d05c5385820c163381e Mon Sep 17 00:00:00 2001 From: KaBooMa Date: Sun, 27 Apr 2025 18:30:07 -0500 Subject: [PATCH 10/13] fix: Resolved missing BepInEx reference --- S1API/S1API.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/S1API/S1API.cs b/S1API/S1API.cs index 117a16d3..68f0a542 100644 --- a/S1API/S1API.cs +++ b/S1API/S1API.cs @@ -12,10 +12,11 @@ public class S1API : MelonMod { } } -#elif (IL2CPPBEPINEX) +#elif (IL2CPPBEPINEX || MONOBEPINEX) using BepInEx; -#elif MONOBEPINEX +#if MONOBEPINEX using BepInEx.Unity.Mono; +#endif using HarmonyLib; From 139408b9fe69fb07e804e21efb98cef7091ec085 Mon Sep 17 00:00:00 2001 From: KaBooMa Date: Sun, 27 Apr 2025 18:31:38 -0500 Subject: [PATCH 11/13] fix: Resolved missing BepInEx reference --- S1API/S1API.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/S1API/S1API.cs b/S1API/S1API.cs index 68f0a542..fdcf8160 100644 --- a/S1API/S1API.cs +++ b/S1API/S1API.cs @@ -14,8 +14,11 @@ public class S1API : MelonMod } #elif (IL2CPPBEPINEX || MONOBEPINEX) using BepInEx; + #if MONOBEPINEX using BepInEx.Unity.Mono; +#elif IL2CPPBEPINEX +using BepInEx.Unity.IL2CPP; #endif using HarmonyLib; From 413b925b1597f1da055ba2d93d14ef65df3c10c4 Mon Sep 17 00:00:00 2001 From: KaBooMa Date: Sun, 27 Apr 2025 18:41:25 -0500 Subject: [PATCH 12/13] fix: Resolved BepInEx Il2Cpp/Mono differences --- S1API/S1API.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/S1API/S1API.cs b/S1API/S1API.cs index fdcf8160..76da5219 100644 --- a/S1API/S1API.cs +++ b/S1API/S1API.cs @@ -26,9 +26,18 @@ public class S1API : MelonMod namespace S1API { [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)] - public class S1API : BaseUnityPlugin + public class S1API : +#if MONOBEPINEX + BaseUnityPlugin +#elif IL2CPPBEPINEX + BasePlugin +#endif { +#if MONOBEPINEX private void Awake() +#elif IL2CPPBEPINEX + public override void Load() +#endif { new Harmony("com.S1API").PatchAll(); } From 6a8b19049afffee07769924caca9eaed2ae4d943 Mon Sep 17 00:00:00 2001 From: KaBooMa Date: Sun, 27 Apr 2025 18:41:45 -0500 Subject: [PATCH 13/13] fix: Disabling BepInEx building until it can be properly built locally --- .github/workflows/deploy-build.yml | 17 +++++++++-------- .github/workflows/verify-build.yml | 13 +++++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy-build.yml b/.github/workflows/deploy-build.yml index 07f4d251..d5b1e4d5 100644 --- a/.github/workflows/deploy-build.yml +++ b/.github/workflows/deploy-build.yml @@ -56,11 +56,12 @@ jobs: sed -i "s/{VERSION_NUMBER}/${{ steps.semantic-release.outputs.new_release_version }}/" ./S1API/S1API.cs sed -i "s/{VERSION_NUMBER}/${{ steps.semantic-release.outputs.new_release_version }}/" ./S1APILoader/S1APILoader.cs - - name: Run .NET build for MonoBepInEx - run: dotnet build ./S1API.sln -c MonoBepInEx -f netstandard2.1 - - - name: Run .NET build for Il2CppBepInEx - run: dotnet build ./S1API.sln -c Il2CppBepInEx -f net6.0 +# TODO (@MaxtorCoder): Temporarily disabling until BepInEx is building properly locally. +# - name: Run .NET build for MonoBepInEx +# run: dotnet build ./S1API.sln -c MonoBepInEx -f netstandard2.1 +# +# - name: Run .NET build for Il2CppBepInEx +# run: dotnet build ./S1API.sln -c Il2CppBepInEx -f net6.0 - name: Run .NET build for MonoMelon run: dotnet build ./S1API.sln -c MonoMelon -f netstandard2.1 @@ -72,11 +73,11 @@ jobs: run: | mkdir -p ./artifacts/thunderstore/Plugins/S1API cp ./S1APILoader/bin/MonoMelon/netstandard2.1/S1APILoader.dll ./artifacts/thunderstore/Plugins/S1APILoader.MelonLoader.dll - cp ./S1APILoader/bin/MonoBepInEx/netstandard2.1/S1APILoader.dll ./artifacts/thunderstore/Plugins/S1APILoader.BepInEx.dll +# cp ./S1APILoader/bin/MonoBepInEx/netstandard2.1/S1APILoader.dll ./artifacts/thunderstore/Plugins/S1APILoader.BepInEx.dll cp ./S1API/bin/Il2CppMelon/net6.0/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Il2Cpp.MelonLoader.dll cp ./S1API/bin/MonoMelon/netstandard2.1/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Mono.MelonLoader.dll - cp ./S1API/bin/Il2CppMelon/net6.0/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Il2Cpp.BepInEx.dll - cp ./S1API/bin/MonoMelon/netstandard2.1/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Mono.BepInEx.dll +# cp ./S1API/bin/Il2CppMelon/net6.0/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Il2Cpp.BepInEx.dll +# cp ./S1API/bin/MonoMelon/netstandard2.1/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Mono.BepInEx.dll - name: Publish artifact to Thunderstore uses: GreenTF/upload-thunderstore-package@v4.3 diff --git a/.github/workflows/verify-build.yml b/.github/workflows/verify-build.yml index 04ae7715..1cdef91b 100644 --- a/.github/workflows/verify-build.yml +++ b/.github/workflows/verify-build.yml @@ -27,12 +27,13 @@ jobs: - name: Restore .NET Dependencies run: dotnet restore - - - name: Run .NET build for MonoBepInEx - run: dotnet build ./S1API.sln -c MonoBepInEx -f netstandard2.1 - - - name: Run .NET build for Il2CppBepInEx - run: dotnet build ./S1API.sln -c Il2CppBepInEx -f net6.0 + +# TODO (@MaxtorCoder): Temporarily disabling until BepInEx is building properly locally. +# - name: Run .NET build for MonoBepInEx +# run: dotnet build ./S1API.sln -c MonoBepInEx -f netstandard2.1 +# +# - name: Run .NET build for Il2CppBepInEx +# run: dotnet build ./S1API.sln -c Il2CppBepInEx -f net6.0 - name: Run .NET build for MonoMelon run: dotnet build ./S1API.sln -c MonoMelon -f netstandard2.1