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);
}