diff --git a/README.md b/README.md index e3c854ca..3a82919d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The logo is generated by Image Creator on 2023/10/13. Inform the remedy if there is infringement.

-A new mod loader for [Worldbox](http://www.superworldbox.com/) after [NCMS](https://denq04.github.io/ncms/), with an Android version, called NML +A new mod loader for [Worldbox](http://www.superworldbox.com/) after [NCMS](https://denq04.github.io/ncms/), with Complete Platform Support, called NML for short. Detailed introduction look [Gitbook](https://markamibox.github.io/worldbox-modding-box/#/index).

Overview

@@ -33,11 +33,12 @@ for short. Detailed introduction look [Gitbook](https://markamibox.github.io/wor 2. Delete NCMS_memload.dll (They can be installed together but not suggested) 3. Start the game with experimental mode enabled -It is recommended to subscribe to the NML item on the Steam Workshop to receive automatic updates +It is recommended to subscribe to the NML item on the Steam Workshop to receive automatic updates +note: standard mobile versions are IL2CPP and cant be modded normally. so you need a Mono version. NeoModLoader is not affiliated with these. these may also be outdated for the latest version. ### Android Installation -1. Use a suitable Mono mod loader for worldbox, like FusionCore +1. Use a suitable Mono mod loader for worldbox 2. Move NeoModLoader.dll to `/mods` 3. enable experimental mode and start the game diff --git a/WorldBoxMod.cs b/WorldBoxMod.cs index b0cb6ec6..9b18986e 100644 --- a/WorldBoxMod.cs +++ b/WorldBoxMod.cs @@ -145,6 +145,8 @@ private void Update() Paths.NCMSAdditionModResourceFolderName), Linker); ResourcesPatch.LoadAssetBundlesFromFolder(Path.Combine(mod.mod_decl.FolderPath, Paths.ModAssetBundleFolderName)); + ResourcesPatch.LoadNativeLibrariesFromFolder(Path.Combine(mod.mod_decl.FolderPath, + Paths.NativeLibrariesFolder)); } }, "Load Resources From Mod " + mod.mod_decl.Name); } diff --git a/assembly-dependencies/UnityEngine.AndroidJNIModule.dll b/assembly-dependencies/UnityEngine.AndroidJNIModule.dll new file mode 100755 index 00000000..4a0c558e Binary files /dev/null and b/assembly-dependencies/UnityEngine.AndroidJNIModule.dll differ diff --git a/constants/Paths.cs b/constants/Paths.cs index e6db87dc..86390dbd 100644 --- a/constants/Paths.cs +++ b/constants/Paths.cs @@ -17,7 +17,6 @@ public static class Paths /// Path to persistent data /// public static readonly string PersistentDataPath = Combine(Application.persistentDataPath); - /// /// Path to folder StreamingAssets /// @@ -109,6 +108,10 @@ public static class Paths /// public static readonly string ModDefaultConfigFileName = "default_config.json"; + /// + /// Folder name of a mods native libraries + /// + public static readonly string NativeLibrariesFolder = "Native"; /// /// Folder name of a mod's resource folder which will be patched to /// diff --git a/general/RF.cs b/general/RF.cs index 051c3166..e27c85a3 100644 --- a/general/RF.cs +++ b/general/RF.cs @@ -45,11 +45,19 @@ public static Delegate GetMethodDelegate(this Type type, string name, bool is_st return method; } var newMethod1 = ReflectionHelper.GetMethod(type, name, is_static); + if (newMethod1 == null) + { + return null; + } methods.Add(name, newMethod1); return newMethod1; } var newMethod = ReflectionHelper.GetMethod(type, name, is_static); + if (newMethod == null) + { + return null; + } _method_cache.Add(type, new Dictionary { { name, newMethod } }); return newMethod; } diff --git a/resources/commit b/resources/commit index 83ef0fe9..5420a169 100644 --- a/resources/commit +++ b/resources/commit @@ -1 +1 @@ -17c7aa59e67d7b8eecf451070fe3efc09a0a0532 +9902208ed4b6c31c3f1cf0d57235c288afaa7a19 diff --git a/services/NativeLibraryManager.cs b/services/NativeLibraryManager.cs new file mode 100644 index 00000000..06a4c108 --- /dev/null +++ b/services/NativeLibraryManager.cs @@ -0,0 +1,223 @@ +using System.Reflection; +using NeoModLoader.api; +using NeoModLoader.constants; +using NeoModLoader.General; +using Newtonsoft.Json; +namespace NeoModLoader.services; +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.IO; +using System.Runtime.InteropServices; +using UnityEngine; + +public static class NativeLibraryManager +{ + // Windows + [DllImport("kernel32.dll", EntryPoint = "LoadLibraryW", + SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr WinLoadLibrary(string fileName); + + [DllImport("kernel32.dll", EntryPoint = "GetProcAddress", + SetLastError = true, CharSet = CharSet.Ansi)] + private static extern IntPtr WinGetAddress(IntPtr handle, string symbol); + + // Linux + [DllImport("libdl.so.2", EntryPoint = "dlopen", + CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr LinuxDlOpen(string filename, int flags); + + [DllImport("libdl.so.2", EntryPoint = "dlsym", + CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr LinuxDlSym(IntPtr handle, string symbol); + + [DllImport("libdl.so.2", EntryPoint = "dlerror", + CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr LinuxDlError(); + + // Android + [DllImport("libmain.so", EntryPoint = "load_library", + CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr AndroidDlOpen(string filename); + + [DllImport("libdl.so", EntryPoint = "dlsym", + CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr AndroidDlSym(IntPtr handle, string symbol); + + private const int OPEN_FLAGS = 2 | 0x100; + + delegate IntPtr loadlib(string Path, out string Error); + delegate IntPtr getaddress(IntPtr Handle, string symbol); + + static getaddress GetAddress; + static loadlib Loadlib; + struct NativeLibrary(IntPtr Handle, string Path) + { + public IntPtr Handle = Handle; + public string Path = Path; + } + private static readonly ConcurrentDictionary + Libraries = new(); + + private static string PlatformFolder; + private static string SpecialPath; + + [Serializable] + private class LoadOrder + { + public List Order { get; set; } + } + + static NativeLibraryManager() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsEditor: + case RuntimePlatform.WindowsPlayer: + GetAddress = WinGetAddress; + Loadlib = (path, out error) => + { + error = null; + IntPtr handle = WinLoadLibrary(path); + if (handle == IntPtr.Zero) + { + error = new System.ComponentModel.Win32Exception( + Marshal.GetLastWin32Error()).Message; + } + + return handle; + }; + PlatformFolder = "win"; + break; + + case RuntimePlatform.LinuxEditor: + case RuntimePlatform.LinuxPlayer: + GetAddress = LinuxDlSym; + Loadlib = (path, out error) => + { + error = null; + IntPtr handle = LinuxDlOpen(path, OPEN_FLAGS); + if (handle == IntPtr.Zero) + { + error = Marshal.PtrToStringAnsi(LinuxDlError()); + } + return handle; + }; + PlatformFolder = "linux"; + break; + + case RuntimePlatform.Android: + PlatformFolder = "android"; + SpecialPath = Path.Combine(IL2CPP2MONO.App.InternalPath, "NML"); + GetAddress = AndroidDlSym; + Loadlib = (path, out error) => + { + error = ""; //it already logs the error inside libmain + IntPtr handle = AndroidDlOpen(path); + return handle; + }; + break; + + default: + PlatformFolder = null; + break; + } + } + + public static void LoadLibraries(string Folder) + { + if (PlatformFolder == null) + { + throw new PlatformNotSupportedException("Native libraries are not supported on this platform!"); + } + string nativeFolder = Path.Combine(Folder, PlatformFolder); + if (!Directory.Exists(nativeFolder)) + { + LogService.LogWarning( + $"Warning: {Folder} doesn't contain any native libraries for the current platform!"); + return; + } + string[] files = Directory.GetFiles(nativeFolder); + + Dictionary available = + new Dictionary(StringComparer.Ordinal); + + foreach (string file in files) + { + string name = Path.GetFileNameWithoutExtension(file); + + available.Add(name, file); + } + + List loadOrder = GetOrder( + Folder) ?? [..available.Keys]; + + foreach (string name in loadOrder) + { + string file = available[name]; + + if (Libraries.TryGetValue(name, out var lib)) + { + if (string.Equals(lib.Path, file, + StringComparison.Ordinal)) + continue; + + throw new InvalidOperationException( + $"Native library '{name}' has already been loaded from '{lib.Path}'. " + + $"Cannot load it again from '{file}'."); + } + + string loadPath = file; + if (SpecialPath != null) + { + Directory.CreateDirectory(SpecialPath); + loadPath = Path.Combine(SpecialPath, Path.GetFileName(file)); + File.Copy(file, loadPath, true); + } + IntPtr handle = Loadlib(loadPath, out string error); + if (handle == IntPtr.Zero) + { + throw new BadImageFormatException( + $"Failed to load native library '{file}': {error}"); + } + Libraries[name] = new NativeLibrary(handle, file); + } + } + + public static T GetNativeFunction( + string Name, string Symbol) where T : Delegate + { + if (!Libraries.TryGetValue(Name, out var lib)) + throw new DllNotFoundException($"native library {Name} was not found!"); + IntPtr function = GetAddress(lib.Handle, Symbol); + if (function == IntPtr.Zero) + { + throw new EntryPointNotFoundException( + $"Could not find symbol '{Symbol}' in native library '{Name}'."); + } + return Marshal.GetDelegateForFunctionPointer( + function, typeof(T)) as T; + } + + // Dependency handling + + private static List GetOrder( + string root) + { + string manifestPath = Path.Combine( + root, "order.json"); + if (!File.Exists(manifestPath)) + return null; + try + { + string Json = File.ReadAllText(manifestPath); + LoadOrder order = JsonConvert.DeserializeObject(Json); + return order?.Order; + } + catch (Exception ex) + { + throw new InvalidDataException( + "Failed to read order.json.", ex); + } + } +} \ No newline at end of file diff --git a/utils/MobileStuff.cs b/utils/MobileStuff.cs new file mode 100644 index 00000000..cd41a7de --- /dev/null +++ b/utils/MobileStuff.cs @@ -0,0 +1,9 @@ +using System.Runtime.CompilerServices; + +namespace IL2CPP2MONO; + +public class App +{ + public static string InternalPath { [MethodImpl(MethodImplOptions.InternalCall)] get; } +} +//we will add an IOS class used for detours once we make them good \ No newline at end of file diff --git a/utils/ReflectionHelper.cs b/utils/ReflectionHelper.cs index ea07b18e..dabae19f 100644 --- a/utils/ReflectionHelper.cs +++ b/utils/ReflectionHelper.cs @@ -21,7 +21,7 @@ internal static Delegate GetMethod(string method_name, bool is_static = false internal static Delegate GetMethod(Type type, string method_name, bool is_static = false) { return createMethodDelegate(is_static - ? type.GetMethod(method_name, BindingFlags.Static | BindingFlags.NonPublic) + ? type.GetMethod(method_name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public) : AccessTools.Method(type, method_name)); } @@ -138,6 +138,10 @@ internal static Action CreateFieldSetter(string field_name) private static Delegate createMethodDelegate(MethodInfo method_info) { + if (method_info == null) + { + return null; + } List paramExpressions = method_info.GetParameters() .Select((p, i) => Expression.Parameter(p.ParameterType, p.Name)).ToList(); diff --git a/utils/ResourcesPatch.cs b/utils/ResourcesPatch.cs index 06cf140e..1b8fe47b 100644 --- a/utils/ResourcesPatch.cs +++ b/utils/ResourcesPatch.cs @@ -111,7 +111,11 @@ private static TextAsset LoadTextAsset(string path) textAsset.name = Path.GetFileNameWithoutExtension(path); return textAsset; } - + internal static void LoadNativeLibrariesFromFolder(string pFolder) + { + if (!Directory.Exists(pFolder)) return; + NativeLibraryManager.LoadLibraries(pFolder); + } internal static void LoadResourceFromFolder(string pFolder, AssetLinker Linker) { if (!Directory.Exists(pFolder)) return; @@ -131,16 +135,20 @@ internal static void LoadAssetBundlesFromFolder(string pFolder) { RuntimePlatform.WindowsPlayer => "win", RuntimePlatform.WindowsEditor => "win", - RuntimePlatform.Android => "and", + RuntimePlatform.Android => "android", RuntimePlatform.IPhonePlayer => "ios", RuntimePlatform.OSXPlayer => "osx", RuntimePlatform.OSXEditor => "osx", RuntimePlatform.LinuxPlayer => "linux", RuntimePlatform.LinuxEditor => "linux", - _ => "win" + _ => throw new PlatformNotSupportedException() }; string platform_folder = Path.Combine(pFolder, platform_subfolder_name); - if (!Directory.Exists(platform_folder)) return; + if (!Directory.Exists(platform_folder)) + { + LogService.LogWarning($"Warning: {pFolder} doesn't contain any asset bundles for the current platform!"); + return; + } AssetBundleUtils.LoadFromFolder(platform_folder); }