diff --git a/S1API/AssetBundles/AssetLoader.cs b/S1API/AssetBundles/AssetLoader.cs new file mode 100644 index 00000000..15c4f590 --- /dev/null +++ b/S1API/AssetBundles/AssetLoader.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +#if IL2CPPBEPINEX || IL2CPPMELON +using System.IO; +#endif + +using UnityEngine; +using Object = UnityEngine.Object; + +using S1API.Logging; + +namespace S1API.AssetBundles +{ + /// + /// The asset bundle manager + /// + public static class AssetLoader + { + private static readonly Log _logger = new Log("AssetLoader"); + private static readonly Dictionary _cachedAssetBundles = new Dictionary(); + +#if IL2CPPMELON || IL2CPPBEPINEX + /// + /// Loads an Il2Cpp AssetBundle from an embedded resource stream by name. + /// + /// The full embedded resource name (including namespace path). + /// The assembly to load the embedded resource from. + /// The loaded Il2CppAssetBundle, or throws on failure. + public static WrappedAssetBundle GetAssetBundleFromStream(string fullResourceName, Assembly overrideAssembly) + { + if (_cachedAssetBundles.TryGetValue(fullResourceName, out WrappedAssetBundle cachedWrappedAssetBundle)) + return cachedWrappedAssetBundle; + + // Attempt to find the embedded resource in the executing assembly + using Stream? stream = overrideAssembly.GetManifestResourceStream(fullResourceName); + if (stream == null) + throw new Exception($"Embedded resource '{fullResourceName}' not found in {overrideAssembly.FullName}."); // hoping these throws will be melon/bepinex-agnostic + + // Read the stream into a byte array + byte[] data = new byte[stream.Length]; + _ = stream.Read(data, 0, data.Length); + + // Load the AssetBundle from memory + Il2CppAssetBundle bundle = Il2CppAssetBundleManager.LoadFromMemory(data); + if (bundle == null) + throw new Exception($"Failed to load AssetBundle from memory: {fullResourceName}"); + + WrappedAssetBundle wrappedAssetBundle = new WrappedAssetBundle(bundle); + _cachedAssetBundles.TryAdd(fullResourceName, wrappedAssetBundle); + return wrappedAssetBundle; + } +#elif MONOMELON || MONOBEPINEX + /// + /// Load a instance by resource name. + /// + /// The full embedded resource name (including namespace path); + /// The assembly to load the embedded resource from. + /// The loaded AssetBundle instance + public static WrappedAssetBundle GetAssetBundleFromStream(string fullResourceName, Assembly overrideAssembly) + { + // Attempt to retrieve the cached asset bundle + if (_cachedAssetBundles.TryGetValue(fullResourceName, out WrappedAssetBundle cachedWrappedAssetBundle)) + return cachedWrappedAssetBundle; + + // Attempt to find the embedded resource in the executing assembly + var stream = overrideAssembly.GetManifestResourceStream(fullResourceName); + + WrappedAssetBundle wrappedAssetBundle = new WrappedAssetBundle(AssetBundle.LoadFromStream(stream)); + _cachedAssetBundles.TryAdd(fullResourceName, wrappedAssetBundle); + return wrappedAssetBundle; + } +#endif + + /// + /// Loads an asset of type from an embedded AssetBundle using the executing assembly. + /// + /// The type of asset to load (must derive from UnityEngine.Object). + /// The name of the embedded AssetBundle resource. + /// The name of the asset to load within the AssetBundle. + /// The loaded asset of type . + public static T EasyLoad(string bundleName, string objectName) where T : Object + { + return EasyLoad(bundleName, objectName, Assembly.GetExecutingAssembly(), out _); + } + + /// + /// Loads an asset of type from an embedded AssetBundle using the executing assembly and outputs the loaded bundle. + /// + /// The type of asset to load (must derive from UnityEngine.Object). + /// The name of the embedded AssetBundle resource. + /// The name of the asset to load within the AssetBundle. + /// The output parameter containing the loaded . + /// The loaded asset of type . + public static T EasyLoad(string bundleName, string objectName, out WrappedAssetBundle bundle) where T : Object + { + return EasyLoad(bundleName, objectName, Assembly.GetExecutingAssembly(), out bundle); + } + + /// + /// Loads an asset of type from an embedded AssetBundle using a specified assembly. + /// + /// The type of asset to load (must derive from UnityEngine.Object). + /// The name of the embedded AssetBundle resource. + /// The name of the asset to load within the AssetBundle. + /// The assembly from which to load the embedded AssetBundle resource. + /// The loaded asset of type . + public static T EasyLoad(string bundleName, string objectName, Assembly assemblyOverride) where T : Object + { + return EasyLoad(bundleName, objectName, assemblyOverride, out _); + } + + /// + /// Loads an asset of type from an embedded AssetBundle using a specified assembly and outputs the loaded bundle. + /// + /// The type of asset to load (must derive from UnityEngine.Object). + /// The name of the embedded AssetBundle resource. + /// The name of the asset to load within the AssetBundle. + /// The assembly from which to load the embedded AssetBundle resource. + /// The output parameter containing the loaded . + /// The loaded asset of type . + public static T EasyLoad(string bundleName, string objectName, Assembly assemblyOverride, out WrappedAssetBundle bundle) where T : Object + { + // Get the asset bundle from the assembly + bundle = GetAssetBundleFromStream($"{assemblyOverride.GetName().Name}.{bundleName}", assemblyOverride); + + // Load the asset from the bundle + return bundle.LoadAsset(objectName); + } + } +} diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundle.cs b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundle.cs new file mode 100644 index 00000000..b2acf95a --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundle.cs @@ -0,0 +1,286 @@ +#if IL2CPPBEPINEX +using System; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppInterop.Runtime.InteropTypes; + +namespace UnityEngine +{ + public class Il2CppAssetBundle + { + private IntPtr bundleptr = IntPtr.Zero; + + public Il2CppAssetBundle(IntPtr ptr) { bundleptr = ptr; } + + static Il2CppAssetBundle() + { + get_isStreamedSceneAssetBundleDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::get_isStreamedSceneAssetBundle"); + returnMainAssetDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::returnMainAsset"); + ContainsDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::Contains"); + GetAllAssetNamesDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::GetAllAssetNames"); + GetAllScenePathsDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::GetAllScenePaths"); + LoadAsset_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadAsset_Internal(System.String,System.Type)"); + LoadAssetAsync_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadAssetAsync_Internal"); + LoadAssetWithSubAssets_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadAssetWithSubAssets_Internal"); + LoadAssetWithSubAssetsAsync_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadAssetWithSubAssetsAsync_Internal"); + UnloadDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::Unload"); + } + + public bool isStreamedSceneAssetBundle + { + get + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (get_isStreamedSceneAssetBundleDelegateField == null) + throw new NullReferenceException("The get_isStreamedSceneAssetBundleDelegateField cannot be null."); + return get_isStreamedSceneAssetBundleDelegateField(bundleptr); + } + } + + public Object mainAsset + { + get + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (returnMainAssetDelegateField == null) + throw new NullReferenceException("The returnMainAssetDelegateField cannot be null."); + IntPtr intPtr = returnMainAssetDelegateField(bundleptr); + return ((intPtr != IntPtr.Zero) ? new Object(intPtr) : null); + } + } + + public bool Contains(string name) + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("The input asset name cannot be null or empty."); + if (ContainsDelegateField == null) + throw new NullReferenceException("The ContainsDelegateField cannot be null."); + return ContainsDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name)); + } + + public Il2CppStringArray AllAssetNames() => GetAllAssetNames(); + + public Il2CppStringArray GetAllAssetNames() + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (GetAllAssetNamesDelegateField == null) + throw new NullReferenceException("The GetAllAssetNamesDelegateField cannot be null."); + IntPtr intPtr = GetAllAssetNamesDelegateField(bundleptr); + return ((intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null); + } + + public Il2CppStringArray AllScenePaths() => GetAllScenePaths(); + + public Il2CppStringArray GetAllScenePaths() + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (GetAllScenePathsDelegateField == null) + throw new NullReferenceException("The GetAllScenePathsDelegateField cannot be null."); + IntPtr intPtr = GetAllScenePathsDelegateField(bundleptr); + return ((intPtr != IntPtr.Zero) ? new Il2CppStringArray(intPtr) : null); + } + + public Object Load(string name) => LoadAsset(name); + + public Object LoadAsset(string name) => LoadAsset(name); + + public T Load(string name) where T : Object => LoadAsset(name); + + public T LoadAsset(string name) where T : Object + { + if (!InteropUtils.IsGeneratedAssemblyType(typeof(T))) + throw new NullReferenceException("The type must be a Generated Assembly Type."); + IntPtr intptr = LoadAsset(name, Il2CppType.Of().Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppObjectBase(intptr).Cast() : null); + } + + public Object Load(string name, Il2CppSystem.Type type) => LoadAsset(name, type); + + public Object LoadAsset(string name, Il2CppSystem.Type type) + { + if (type == null) + throw new NullReferenceException("The input type cannot be null."); + IntPtr intptr = LoadAsset(name, type.Pointer); + return ((intptr != IntPtr.Zero) ? new Object(intptr) : null); + } + + public IntPtr Load(string name, IntPtr typeptr) => LoadAsset(name, typeptr); + + public IntPtr LoadAsset(string name, IntPtr typeptr) + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("The input asset name cannot be null or empty."); + if (typeptr == IntPtr.Zero) + throw new NullReferenceException("The input type cannot be IntPtr.Zero"); + if (LoadAsset_InternalDelegateField == null) + throw new NullReferenceException("The LoadAsset_InternalDelegateField cannot be null."); + return LoadAsset_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + } + + public Il2CppAssetBundleRequest LoadAssetAsync(string name) => LoadAssetAsync(name); + + public Il2CppAssetBundleRequest LoadAssetAsync(string name) where T : Object + { + if (!InteropUtils.IsGeneratedAssemblyType(typeof(T))) + throw new NullReferenceException("The type must be a Generated Assembly Type."); + IntPtr intptr = LoadAssetAsync(name, Il2CppType.Of().Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + } + + public Il2CppAssetBundleRequest LoadAssetAsync(string name, Il2CppSystem.Type type) + { + if (type == null) + throw new NullReferenceException("The input type cannot be null."); + IntPtr intptr = LoadAssetAsync(name, type.Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + } + + public IntPtr LoadAssetAsync(string name, IntPtr typeptr) + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("The input asset name cannot be null or empty."); + if (typeptr == IntPtr.Zero) + throw new NullReferenceException("The input type cannot be IntPtr.Zero"); + if (LoadAssetAsync_InternalDelegateField == null) + throw new NullReferenceException("The LoadAssetAsync_InternalDelegateField cannot be null."); + return LoadAssetAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + } + + public Il2CppReferenceArray LoadAll() => LoadAllAssets(); + + public Il2CppReferenceArray LoadAllAssets() => LoadAllAssets(); + + public Il2CppReferenceArray LoadAll() where T : Object => LoadAllAssets(); + + public Il2CppReferenceArray LoadAllAssets() where T : Object + { + if (!InteropUtils.IsGeneratedAssemblyType(typeof(T))) + throw new NullReferenceException("The type must be a Generated Assembly Type."); + IntPtr intptr = LoadAllAssets(Il2CppType.Of().Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + } + + public Il2CppReferenceArray LoadAll(Il2CppSystem.Type type) => LoadAllAssets(type); + + public Il2CppReferenceArray LoadAllAssets(Il2CppSystem.Type type) + { + if (type == null) + throw new NullReferenceException("The input type cannot be null."); + IntPtr intptr = LoadAllAssets(type.Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + } + + public IntPtr LoadAll(IntPtr typeptr) => LoadAllAssets(typeptr); + + public IntPtr LoadAllAssets(IntPtr typeptr) + { + if (typeptr == IntPtr.Zero) + throw new NullReferenceException("The input type cannot be IntPtr.Zero"); + if (LoadAssetWithSubAssets_InternalDelegateField == null) + throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null."); + return LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(string.Empty), typeptr); + } + + public Il2CppReferenceArray LoadAssetWithSubAssets(string name) => LoadAssetWithSubAssets(name); + + public Il2CppReferenceArray LoadAssetWithSubAssets(string name) where T : Object + { + if (!InteropUtils.IsGeneratedAssemblyType(typeof(T))) + throw new NullReferenceException("The type must be a Generated Assembly Type."); + IntPtr intptr = LoadAssetWithSubAssets(name, Il2CppType.Of().Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + } + + public Il2CppReferenceArray LoadAssetWithSubAssets(string name, Il2CppSystem.Type type) + { + if (type == null) + throw new NullReferenceException("The input type cannot be null."); + IntPtr intptr = LoadAssetWithSubAssets(name, type.Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppReferenceArray(intptr) : null); + } + + public IntPtr LoadAssetWithSubAssets(string name, IntPtr typeptr) + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("The input asset name cannot be null or empty."); + if (typeptr == IntPtr.Zero) + throw new NullReferenceException("The input type cannot be IntPtr.Zero"); + if (LoadAssetWithSubAssets_InternalDelegateField == null) + throw new NullReferenceException("The LoadAssetWithSubAssets_InternalDelegateField cannot be null."); + return LoadAssetWithSubAssets_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + } + public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name) => LoadAssetWithSubAssetsAsync(name); + + public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name) where T : Object + { + if (!InteropUtils.IsGeneratedAssemblyType(typeof(T))) + throw new NullReferenceException("The type must be a Generated Assembly Type."); + IntPtr intptr = LoadAssetWithSubAssetsAsync(name, Il2CppType.Of().Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + } + + public Il2CppAssetBundleRequest LoadAssetWithSubAssetsAsync(string name, Il2CppSystem.Type type) + { + if (type == null) + throw new NullReferenceException("The input type cannot be null."); + IntPtr intptr = LoadAssetWithSubAssetsAsync(name, type.Pointer); + return ((intptr != IntPtr.Zero) ? new Il2CppAssetBundleRequest(intptr) : null); + } + + public IntPtr LoadAssetWithSubAssetsAsync(string name, IntPtr typeptr) + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("The input asset name cannot be null or empty."); + if (typeptr == IntPtr.Zero) + throw new NullReferenceException("The input type cannot be IntPtr.Zero"); + if (LoadAssetWithSubAssetsAsync_InternalDelegateField == null) + throw new NullReferenceException("The LoadAssetWithSubAssetsAsync_InternalDelegateField cannot be null."); + return LoadAssetWithSubAssetsAsync_InternalDelegateField(bundleptr, IL2CPP.ManagedStringToIl2Cpp(name), typeptr); + } + + public void Unload(bool unloadAllLoadedObjects) + { + if (bundleptr == IntPtr.Zero) + throw new NullReferenceException("The bundleptr cannot be IntPtr.Zero"); + if (UnloadDelegateField == null) + throw new NullReferenceException("The UnloadDelegateField cannot be null."); + UnloadDelegateField(bundleptr, unloadAllLoadedObjects); + } + + private delegate bool get_isStreamedSceneAssetBundleDelegate(IntPtr _this); + private static readonly returnMainAssetDelegate returnMainAssetDelegateField; + private delegate IntPtr returnMainAssetDelegate(IntPtr _this); + private static readonly get_isStreamedSceneAssetBundleDelegate get_isStreamedSceneAssetBundleDelegateField; + private delegate bool ContainsDelegate(IntPtr _this, IntPtr name); + private static readonly ContainsDelegate ContainsDelegateField; + private delegate IntPtr GetAllAssetNamesDelegate(IntPtr _this); + private static readonly GetAllAssetNamesDelegate GetAllAssetNamesDelegateField; + private delegate IntPtr GetAllScenePathsDelegate(IntPtr _this); + private static readonly GetAllScenePathsDelegate GetAllScenePathsDelegateField; + private delegate IntPtr LoadAsset_InternalDelegate(IntPtr _this, IntPtr name, IntPtr type); + private static readonly LoadAsset_InternalDelegate LoadAsset_InternalDelegateField; + private delegate IntPtr LoadAssetAsync_InternalDelegate(IntPtr _this, IntPtr name, IntPtr type); + private static readonly LoadAssetAsync_InternalDelegate LoadAssetAsync_InternalDelegateField; + private delegate IntPtr LoadAssetWithSubAssets_InternalDelegate(IntPtr _this, IntPtr name, IntPtr type); + private static readonly LoadAssetWithSubAssets_InternalDelegate LoadAssetWithSubAssets_InternalDelegateField; + private delegate IntPtr LoadAssetWithSubAssetsAsync_InternalDelegate(IntPtr _this, IntPtr name, IntPtr type); + private static readonly LoadAssetWithSubAssetsAsync_InternalDelegate LoadAssetWithSubAssetsAsync_InternalDelegateField; + private delegate void UnloadDelegate(IntPtr _this, bool unloadAllObjects); + private static readonly UnloadDelegate UnloadDelegateField; + } +} +#endif diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs new file mode 100644 index 00000000..d3307449 --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundleManager.cs @@ -0,0 +1,141 @@ +#if IL2CPPBEPINEX +using Il2CppSystem.IO; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace UnityEngine +{ + public class Il2CppAssetBundleManager + { + static Il2CppAssetBundleManager() + { + GetAllLoadedAssetBundles_NativeDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::GetAllLoadedAssetBundles_Native"); + LoadFromFile_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromFile_Internal(System.String,System.UInt32,System.UInt64)"); + LoadFromFileAsync_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromFileAsync_Internal"); + LoadFromMemory_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromMemory_Internal"); + LoadFromMemoryAsync_InternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromMemoryAsync_Internal"); + LoadFromStreamInternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromStreamInternal"); + LoadFromStreamAsyncInternalDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::LoadFromStreamAsyncInternal"); + UnloadAllAssetBundlesDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundle::UnloadAllAssetBundles"); + } + + public static Il2CppAssetBundle[] GetAllLoadedAssetBundles() + { + if (GetAllLoadedAssetBundles_NativeDelegateField == null) + throw new System.NullReferenceException("The GetAllLoadedAssetBundles_NativeDelegateField cannot be null."); + System.IntPtr intPtr = GetAllLoadedAssetBundles_NativeDelegateField(); + Il2CppReferenceArray refarr = ((intPtr != System.IntPtr.Zero) ? new Il2CppReferenceArray(intPtr) : null); + if (refarr == null) + throw new System.NullReferenceException("The refarr cannot be null."); + System.Collections.Generic.List bundlelist = new System.Collections.Generic.List(); + for (int i = 0; i < refarr.Length; i++) + bundlelist.Add(new Il2CppAssetBundle(IL2CPP.Il2CppObjectBaseToPtrNotNull(refarr[i]))); + return bundlelist.ToArray(); + } + + public static Il2CppAssetBundle LoadFromFile(string path) => LoadFromFile(path, 0u, 0UL); + + public static Il2CppAssetBundle LoadFromFile(string path, uint crc) => LoadFromFile(path, crc, 0UL); + + public static Il2CppAssetBundle LoadFromFile(string path, uint crc, ulong offset) + { + if (string.IsNullOrEmpty(path)) + throw new System.ArgumentException("The input asset bundle path cannot be null or empty."); + if (LoadFromFile_InternalDelegateField == null) + throw new System.NullReferenceException("The LoadFromFile_InternalDelegateField cannot be null."); + System.IntPtr intPtr = LoadFromFile_InternalDelegateField(IL2CPP.ManagedStringToIl2Cpp(path), crc, offset); + return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null); + } + + public static Il2CppAssetBundleCreateRequest LoadFromFileAsync(string path) => LoadFromFileAsync(path, 0u, 0UL); + + public static Il2CppAssetBundleCreateRequest LoadFromFileAsync(string path, uint crc) => LoadFromFileAsync(path, crc, 0UL); + + public static Il2CppAssetBundleCreateRequest LoadFromFileAsync(string path, uint crc, ulong offset) + { + if (string.IsNullOrEmpty(path)) + throw new System.ArgumentException("The input asset bundle path cannot be null or empty."); + if (LoadFromFileAsync_InternalDelegateField == null) + throw new System.NullReferenceException("The LoadFromFileAsync_InternalDelegateField cannot be null."); + System.IntPtr intPtr = LoadFromFileAsync_InternalDelegateField(IL2CPP.ManagedStringToIl2Cpp(path), crc, offset); + return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null); + } + + public static Il2CppAssetBundle LoadFromMemory(Il2CppStructArray binary) => LoadFromMemory(binary, 0u); + + public static Il2CppAssetBundle LoadFromMemory(Il2CppStructArray binary, uint crc) + { + if (binary == null) + throw new System.ArgumentException("The binary cannot be null or empty."); + if (LoadFromMemory_InternalDelegateField == null) + throw new System.NullReferenceException("The LoadFromMemory_InternalDelegateField cannot be null."); + System.IntPtr intPtr = LoadFromMemory_InternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(binary), crc); + return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null); + } + + public static Il2CppAssetBundleCreateRequest LoadFromMemoryAsync(Il2CppStructArray binary) => LoadFromMemoryAsync(binary, 0u); + + public static Il2CppAssetBundleCreateRequest LoadFromMemoryAsync(Il2CppStructArray binary, uint crc) + { + if (binary == null) + throw new System.ArgumentException("The binary cannot be null or empty."); + if (LoadFromMemoryAsync_InternalDelegateField == null) + throw new System.NullReferenceException("The LoadFromMemoryAsync_InternalDelegateField cannot be null."); + System.IntPtr intPtr = LoadFromMemoryAsync_InternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(binary), crc); + return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null); + } + + public static Il2CppAssetBundle LoadFromStream(Stream stream) => LoadFromStream(stream, 0u, 0u); + + public static Il2CppAssetBundle LoadFromStream(Stream stream, uint crc) => LoadFromStream(stream, crc, 0u); + + public static Il2CppAssetBundle LoadFromStream(Stream stream, uint crc, uint managedReadBufferSize) + { + if (stream == null) + throw new System.ArgumentException("The stream cannot be null or empty."); + if (LoadFromStreamInternalDelegateField == null) + throw new System.NullReferenceException("The LoadFromStreamInternalDelegateField cannot be null."); + System.IntPtr intPtr = LoadFromStreamInternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(stream), crc, managedReadBufferSize); + return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundle(intPtr) : null); + } + + public static Il2CppAssetBundleCreateRequest LoadFromStreamAsync(Stream stream) => LoadFromStreamAsync(stream, 0u, 0u); + + public static Il2CppAssetBundleCreateRequest LoadFromStreamAsync(Stream stream, uint crc) => LoadFromStreamAsync(stream, crc, 0u); + + public static Il2CppAssetBundleCreateRequest LoadFromStreamAsync(Stream stream, uint crc, uint managedReadBufferSize) + { + if (stream == null) + throw new System.ArgumentException("The stream cannot be null or empty."); + if (LoadFromStreamAsyncInternalDelegateField == null) + throw new System.NullReferenceException("The LoadFromStreamAsyncInternalDelegateField cannot be null."); + System.IntPtr intPtr = LoadFromStreamAsyncInternalDelegateField(IL2CPP.Il2CppObjectBaseToPtrNotNull(stream), crc, managedReadBufferSize); + return ((intPtr != System.IntPtr.Zero) ? new Il2CppAssetBundleCreateRequest(intPtr) : null); + } + + public static void UnloadAllAssetBundles(bool unloadAllObjects) + { + if (UnloadAllAssetBundlesDelegateField == null) + throw new System.NullReferenceException("The UnloadAllAssetBundlesDelegateField cannot be null."); + UnloadAllAssetBundlesDelegateField(unloadAllObjects); + } + + private delegate System.IntPtr GetAllLoadedAssetBundles_NativeDelegate(); + private static readonly GetAllLoadedAssetBundles_NativeDelegate GetAllLoadedAssetBundles_NativeDelegateField; + private delegate System.IntPtr LoadFromFile_InternalDelegate(System.IntPtr path, uint crc, ulong offset); + private static readonly LoadFromFile_InternalDelegate LoadFromFile_InternalDelegateField; + private delegate System.IntPtr LoadFromFileAsync_InternalDelegate(System.IntPtr path, uint crc, ulong offset); + private static readonly LoadFromFileAsync_InternalDelegate LoadFromFileAsync_InternalDelegateField; + private delegate System.IntPtr LoadFromMemory_InternalDelegate(System.IntPtr binary, uint crc); + private static readonly LoadFromMemory_InternalDelegate LoadFromMemory_InternalDelegateField; + private delegate System.IntPtr LoadFromMemoryAsync_InternalDelegate(System.IntPtr binary, uint crc); + private static readonly LoadFromMemoryAsync_InternalDelegate LoadFromMemoryAsync_InternalDelegateField; + private delegate System.IntPtr LoadFromStreamInternalDelegate(System.IntPtr stream, uint crc, uint managedReadBufferSize); + private static readonly LoadFromStreamInternalDelegate LoadFromStreamInternalDelegateField; + private delegate System.IntPtr LoadFromStreamAsyncInternalDelegate(System.IntPtr stream, uint crc, uint managedReadBufferSize); + private static readonly LoadFromStreamAsyncInternalDelegate LoadFromStreamAsyncInternalDelegateField; + private delegate System.IntPtr UnloadAllAssetBundlesDelegate(bool unloadAllObjects); + private static readonly UnloadAllAssetBundlesDelegate UnloadAllAssetBundlesDelegateField; + } +} +#endif diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs new file mode 100644 index 00000000..41489e45 --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/Il2CppAssetBundleRequest.cs @@ -0,0 +1,76 @@ +#if IL2CPPBEPINEX +using System; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.InteropTypes.Arrays; + +namespace UnityEngine +{ + public class Il2CppAssetBundleCreateRequest : AsyncOperation + { + public Il2CppAssetBundleCreateRequest(IntPtr ptr) : base(ptr) { } + + static Il2CppAssetBundleCreateRequest() + { + Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp(); + + get_assetBundleDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundleCreateRequest::get_assetBundle"); + } + + public Il2CppAssetBundle assetBundle + { + [Il2CppInterop.Runtime.Attributes.HideFromIl2Cpp] + get + { + var ptr = get_assetBundleDelegateField(this.Pointer); + if (ptr == IntPtr.Zero) + return null; + return new Il2CppAssetBundle(ptr); + } + } + + private delegate IntPtr get_assetBundleDelegate(IntPtr _this); + private static get_assetBundleDelegate get_assetBundleDelegateField; + } + + public class Il2CppAssetBundleRequest : AsyncOperation + { + public Il2CppAssetBundleRequest(IntPtr ptr) : base(ptr) { } + + static Il2CppAssetBundleRequest() + { + Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp(); + + get_assetDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundleRequest::get_asset"); + get_allAssetsDelegateField = IL2CPP.ResolveICall("UnityEngine.AssetBundleRequest::get_allAssets"); + } + + public Object asset + { + get + { + var ptr = get_assetDelegateField(this.Pointer); + if (ptr == IntPtr.Zero) + return null; + return new Object(ptr); + } + } + + public Il2CppReferenceArray allAssets + { + get + { + var ptr = get_allAssetsDelegateField(this.Pointer); + if (ptr == IntPtr.Zero) + return null; + return new Il2CppReferenceArray(ptr); + } + } + + private delegate IntPtr get_assetDelegate(IntPtr _this); + private static get_assetDelegate get_assetDelegateField; + + private delegate IntPtr get_allAssetsDelegate(IntPtr _this); + private static get_allAssetsDelegate get_allAssetsDelegateField; + } +} +#endif diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/InteropUtils.cs b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/InteropUtils.cs new file mode 100644 index 00000000..546dd8eb --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/InteropUtils.cs @@ -0,0 +1,30 @@ +#if IL2CPPBEPINEX +using Il2CppInterop.Runtime.InteropTypes; +using Il2CppInterop.Runtime; +using System; + +namespace UnityEngine +{ + internal static class InteropUtils + { + public static bool IsGeneratedAssemblyType(Type type) + => IsInheritedFromIl2CppObjectBase(type) && !IsInjectedType(type); + + public static bool IsInheritedFromIl2CppObjectBase(Type type) + => (type != null) && type.IsSubclassOf(typeof(Il2CppObjectBase)); + + public static bool IsInjectedType(Type type) + { + IntPtr ptr = GetClassPointerForType(type); + return ptr != IntPtr.Zero && RuntimeSpecificsStore.IsInjected(ptr); + } + + public static IntPtr GetClassPointerForType(Type type) + { + if (type == typeof(void)) return Il2CppClassPointerStore.NativeClassPtr; + return (IntPtr)typeof(Il2CppClassPointerStore<>).MakeGenericType(type) + .GetField(nameof(Il2CppClassPointerStore.NativeClassPtr)).GetValue(null); + } + } +} +#endif diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/LICENSE.txt b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/LICENSE.txt new file mode 100644 index 00000000..1becba2b --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/LICENSE.txt @@ -0,0 +1 @@ +404: Not Found \ No newline at end of file diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/NOTICE.md b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/NOTICE.md new file mode 100644 index 00000000..1becba2b --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/NOTICE.md @@ -0,0 +1 @@ +404: Not Found \ No newline at end of file diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/README.md b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/README.md new file mode 100644 index 00000000..d36caea7 --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/README.md @@ -0,0 +1,15 @@ +### GENERAL INFORMATION: + +- Il2Cpp Asset Bundle Manager for [Il2CppInterop](https://github.com/Bepinex/Il2CppInterop) and [BepInEx](https://github.com/BepInEx/BepInEx). +- Originally created by LavaGang for [MelonLoader](https://github.com/LavaGang/MelonLoader). + +--- + +### LICENSING & CREDITS: + +Modified by XmusJackson to support BepInEx. Tested with Schedule I 0.3.4f8. + +UnityEngine.Il2CppAssetBundleManager is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/LavaGang/UnityEngine.Il2CppAssetBundleManager/blob/master/LICENSE.md) for the full License. + +UnityEngine.Il2CppAssetBundleManager is not sponsored by, affiliated with or endorsed by Unity Technologies or its affiliates. +"Unity" is a trademark or a registered trademark of Unity Technologies or its affiliates in the U.S. and elsewhere. diff --git a/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/UpdateBepInExBundleManager.bat b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/UpdateBepInExBundleManager.bat new file mode 100644 index 00000000..8129d5b9 --- /dev/null +++ b/S1API/AssetBundles/UnityEngine.BE.Il2CppAssetBundleManager/UpdateBepInExBundleManager.bat @@ -0,0 +1,42 @@ +@echo off +setlocal enabledelayedexpansion + +REM Base URL for raw files on GitHub (master branch) +set "BASE=https://raw.githubusercontent.com/xmusjackson/UnityEngine.BE.Il2CppAssetBundleManager/master" + +REM List of files to download +set "FILES=Il2CppAssetBundle.cs Il2CppAssetBundleManager.cs Il2CppAssetBundleRequest.cs InteropUtils.cs README.md LICENSE.txt NOTICE.md" + +for %%F in (%FILES%) do ( + echo -------------------------------------------------- + echo Downloading %%F... + curl -sSL "%BASE%/%%F" -o "%%F" + + if errorlevel 1 ( + echo [FAIL] %%F could not be downloaded. + ) else ( + echo [ OK ] %%F downloaded. + + REM Only wrap .cs files + if /I "%%~xF"==".cs" ( + echo ▶ Wrapping %%F with #if…#endif… + + REM Move original to a temp file + move /Y "%%F" "%%F.tmp" >nul + + REM Recreate with wrapper + ( + echo #if IL2CPPBEPINEX + type "%%F.tmp" + echo #endif + ) > "%%F" + + del "%%F.tmp" + echo ✔ Wrapped %%F + ) + ) +) + +echo. +echo All files processed. +pause diff --git a/S1API/AssetBundles/WrappedAssetBundle.cs b/S1API/AssetBundles/WrappedAssetBundle.cs new file mode 100644 index 00000000..58719f3b --- /dev/null +++ b/S1API/AssetBundles/WrappedAssetBundle.cs @@ -0,0 +1,119 @@ +using UnityEngine; + +#if IL2CPPMELON || IL2CPPBEPINEX +using Il2CppSystem; +using AssetBundle = UnityEngine.Il2CppAssetBundle; +using AssetBundleRequest = UnityEngine.Il2CppAssetBundleRequest; +#else +using System; +#endif + +using Object = UnityEngine.Object; + +namespace S1API.AssetBundles +{ + /// + /// INTERNAL: Wrapper around instance. + /// + public class WrappedAssetBundle + { + public bool IsStreamedAssetBundle => _realBundle.isStreamedSceneAssetBundle; + + public AssetBundle _realBundle; + + public WrappedAssetBundle(AssetBundle realBundle) + { + _realBundle = realBundle; + } + + public bool Contains(string name) => _realBundle.Contains(name); + + public string[] GetAllAssetNames() => _realBundle.GetAllAssetNames(); + + public string[] GetAllScenePaths() => _realBundle.GetAllScenePaths(); + + public Object Load(string name) => LoadAsset(name); + + public Object LoadAsset(string name) => LoadAsset(name); + + public T Load(string name) where T : Object => LoadAsset(name); + + public T LoadAsset(string name) where T : Object => _realBundle.LoadAsset(name); + + public Object Load(string name, Type type) => LoadAsset(name, type); + + public Object LoadAsset(string name, Type type) => _realBundle.LoadAsset(name, type); + + public WrappedAssetBundleRequest LoadAssetAsync(string name) => LoadAssetAsync(name); + + public WrappedAssetBundleRequest LoadAssetAsync(string name) where T : Object => + new WrappedAssetBundleRequest(_realBundle.LoadAssetAsync(name)); + + public WrappedAssetBundleRequest LoadAssetAsync(string name, Type type) => + new WrappedAssetBundleRequest(_realBundle.LoadAssetAsync(name, type)); + + public Object[] LoadAll() => LoadAllAssets(); + + public Object[] LoadAllAssets() => LoadAllAssets(); + + public T[] LoadAllAssets() where T : Object => _realBundle.LoadAllAssets(); + + public Object[] LoadAllAssets(Type type) => _realBundle.LoadAllAssets(type); + + public Object[] LoadAssetWithSubAssets(string name) => LoadAssetWithSubAssets(name); + + public T[] LoadAssetWithSubAssets(string name) where T : Object => _realBundle.LoadAssetWithSubAssets(name); + + public Object[] LoadAssetWithSubAssets(string name, Type type) => _realBundle.LoadAssetWithSubAssets(name, type); + + public void Unload(bool unloadAllLoadedObjects) => _realBundle.Unload(unloadAllLoadedObjects); + } +} + +/* Might need this if the above fails + + +public static class Il2CppStringArrayExtensions +{ + public static string[] ToManagedArray(this Il2CppStringArray il2cppStrings) + { + string[] managedStrings = new string[il2cppStrings.Length]; + for (int i = 0; i < il2cppStrings.Length; i++) + { + managedStrings[i] = il2cppStrings[i]; + } + return managedStrings; + } +} + +public static class Il2CppObjectArrayExtensions +{ + public static T[] ToManagedArray(this Il2CppReferenceArray il2cppObjects) where T : Object + { + T[] managedObjects = new T[il2cppObjects.Length]; + for (int i = 0; i < il2cppObjects.Length; i++) + { + managedObjects[i] = il2cppObjects[i]; + } + return managedObjects; + } +} +*/ + + +/* + * + * [12:57:44.463] [ManorMod] Unhandled exception in coroutine. It will not continue executing. +System.MissingMethodException: Method not found: 'UnityEngine.AssetBundle UnityEngine.AssetBundle.LoadFromStream(System.IO.Stream)'. + at S1API.AssetBundles.AssetLoader.GetAssetBundleFromStream(String fullResourceName) + at S1API.AssetBundles.AssetLoader.EasyLoad[T](String bundle_name, String object_name, Assembly assemblyOverride, WrappedAssetBundle& bundle) + at S1API.AssetBundles.AssetLoader.EasyLoad[T](String bundle_name, String object_name) + at ManorMod.Core.LoadAssetBundle()+MoveNext() + at MelonLoader.Support.MonoEnumeratorWrapper.MoveNext() in D:\a\MelonLoader\MelonLoader\Dependencies\SupportModules\Il2Cpp\MonoEnumeratorWrapper.cs:line 39 +[12:57:48.792] [ManorMod] System.MissingMethodException: Method not found: 'Void UnityEngine.Events.UnityAction..ctor(System.Object, IntPtr)'. + at ManorMod.Core.OnLateInitializeMelon() + at MelonLoader.MelonEvent.<>c.b__1_0(LemonAction x) in D:\a\MelonLoader\MelonLoader\MelonLoader\Melons\Events\MelonEvent.cs:line 174 + at MelonLoader.MelonEventBase`1.Invoke(Action`1 delegateInvoker) in D:\a\MelonLoader\MelonLoader\MelonLoader\Melons\Events\MelonEvent.cs:line 143 + + +*/ diff --git a/S1API/AssetBundles/WrappedAssetBundleRequest.cs b/S1API/AssetBundles/WrappedAssetBundleRequest.cs new file mode 100644 index 00000000..fa9dd8eb --- /dev/null +++ b/S1API/AssetBundles/WrappedAssetBundleRequest.cs @@ -0,0 +1,35 @@ +using UnityEngine; + +#if IL2CPPBEPINEX || IL2CPPMELON +using AssetBundleRequest = UnityEngine.Il2CppAssetBundleRequest; +#endif + +namespace S1API.AssetBundles +{ + /// + /// INTERNAL: Wrapper around instance. + /// + public class WrappedAssetBundleRequest + { + private readonly AssetBundleRequest _realRequest; + + /// + /// INTERNAL: Default constructor for + /// + /// + internal WrappedAssetBundleRequest(AssetBundleRequest realRequest) + { + _realRequest = realRequest; + } + + /// + /// The requested asset instance. + /// + public Object Asset => _realRequest.asset; + + /// + /// All Assets in the . + /// + public Object[] AllAssets => _realRequest.allAssets; + } +} diff --git a/S1API/S1API.csproj b/S1API/S1API.csproj index 51ca67e3..c9eaf523 100644 --- a/S1API/S1API.csproj +++ b/S1API/S1API.csproj @@ -41,6 +41,7 @@ + @@ -69,6 +70,7 @@ + @@ -90,7 +92,7 @@ - +