Skip to content
Open
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<small><small>The logo is generated by Image Creator on 2023/10/13. Inform the remedy if there is infringement.</small></small>
</p>

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).

<h2 align="center"> Overview </h2>
Expand All @@ -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 `<Persistentdata>/mods`
3. enable experimental mode and start the game

Expand Down
2 changes: 2 additions & 0 deletions WorldBoxMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Binary file not shown.
5 changes: 4 additions & 1 deletion constants/Paths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public static class Paths
/// Path to persistent data
/// </summary>
public static readonly string PersistentDataPath = Combine(Application.persistentDataPath);

/// <summary>
/// Path to folder StreamingAssets
/// </summary>
Expand Down Expand Up @@ -109,6 +108,10 @@ public static class Paths
/// </summary>
public static readonly string ModDefaultConfigFileName = "default_config.json";

/// <summary>
/// Folder name of a mods native libraries
/// </summary>
public static readonly string NativeLibrariesFolder = "Native";
/// <summary>
/// Folder name of a mod's resource folder which will be patched to <see cref="Resources"/>
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions general/RF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Delegate> { { name, newMethod } });
return newMethod;
}
Expand Down
2 changes: 1 addition & 1 deletion resources/commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17c7aa59e67d7b8eecf451070fe3efc09a0a0532
9902208ed4b6c31c3f1cf0d57235c288afaa7a19
223 changes: 223 additions & 0 deletions services/NativeLibraryManager.cs
Original file line number Diff line number Diff line change
@@ -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<string, NativeLibrary>
Libraries = new();

private static string PlatformFolder;
private static string SpecialPath;

[Serializable]
private class LoadOrder
{
public List<string> 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<string, string> available =
new Dictionary<string, string>(StringComparer.Ordinal);

foreach (string file in files)
{
string name = Path.GetFileNameWithoutExtension(file);

available.Add(name, file);
}

List<string> 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<T>(
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<string> 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<LoadOrder>(Json);
return order?.Order;
}
catch (Exception ex)
{
throw new InvalidDataException(
"Failed to read order.json.", ex);
}
}
}
9 changes: 9 additions & 0 deletions utils/MobileStuff.cs
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion utils/ReflectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static Delegate GetMethod<T>(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));
}

Expand Down Expand Up @@ -138,6 +138,10 @@ internal static Action<TI, TF> CreateFieldSetter<TI, TF>(string field_name)

private static Delegate createMethodDelegate(MethodInfo method_info)
{
if (method_info == null)
{
return null;
}
List<ParameterExpression> paramExpressions = method_info.GetParameters()
.Select((p, i) => Expression.Parameter(p.ParameterType, p.Name)).ToList();

Expand Down
16 changes: 12 additions & 4 deletions utils/ResourcesPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down
Loading