diff --git a/MelonLoader/MelonLoader.Bootstrap/Entrypoint.cs b/MelonLoader/MelonLoader.Bootstrap/Entrypoint.cs index 1e317cf8..d97e6c84 100644 --- a/MelonLoader/MelonLoader.Bootstrap/Entrypoint.cs +++ b/MelonLoader/MelonLoader.Bootstrap/Entrypoint.cs @@ -1,4 +1,6 @@ -using MelonLoader.Utils; +using MelonLoader.Fixes; +using MelonLoader.Utils; +using System.IO; namespace MelonLoader.Bootstrap { @@ -24,6 +26,10 @@ public static unsafe void Entry() MelonDebug.Msg("Found Bootstrap Module: " + module.GetType().FullName); MelonLogger.Msg($"Running Engine: {module.EngineName}"); + + // Fix Resolving Issue + UnhandledAssemblyResolve.AddSearchDirectoryToFront(Path.Combine(MelonEnvironment.ModulesDirectory, module.EngineName, "net6")); + module.Startup(); // TO-DO: Implement Fallback Handling for when a Module Fails to Startup } } diff --git a/MelonLoader/MelonLoader.Shared/Fixes/UnhandledAssemblyResolve.cs b/MelonLoader/MelonLoader.Shared/Fixes/UnhandledAssemblyResolve.cs index 37f62bd1..ebc74b16 100644 --- a/MelonLoader/MelonLoader.Shared/Fixes/UnhandledAssemblyResolve.cs +++ b/MelonLoader/MelonLoader.Shared/Fixes/UnhandledAssemblyResolve.cs @@ -1,5 +1,6 @@ using MelonLoader.Utils; using System; +using System.Collections.Generic; using System.IO; using System.Reflection; @@ -15,7 +16,7 @@ public static class UnhandledAssemblyResolve private static AssemblyLoadContext _alc; #endif - private static string[] SearchableDirectories = new string[] + private static List SearchableDirectories = new List { #if NET6_0 Path.Combine(MelonEnvironment.ModulesDirectory, "Mono", "net6"), @@ -38,7 +39,7 @@ public static class UnhandledAssemblyResolve MelonEnvironment.GameRootDirectory }; - public static void Install() + internal static void Install() { #if NET6_0 AssemblyLoadContext.Default.Resolving += OnResolve; @@ -48,6 +49,31 @@ public static void Install() #endif } + public static void AddSearchDirectoryToFront(string path) + { + if (SearchableDirectories.Contains(path)) + return; + + string[] dirArr = SearchableDirectories.ToArray(); + SearchableDirectories.Clear(); + SearchableDirectories.Add(path); + SearchableDirectories.AddRange(dirArr); + } + + public static void AddSearchDirectory(string path) + { + if (SearchableDirectories.Contains(path)) + return; + SearchableDirectories.Add(path); + } + + public static void RemoveSearchDirectory(string path) + { + if (!SearchableDirectories.Contains(path)) + return; + SearchableDirectories.Remove(path); + } + private static Assembly FindAssembly(string name, Func tryLoad) { var filename = name + ".dll"; @@ -55,11 +81,14 @@ private static Assembly FindAssembly(string name, Func tryLoad Assembly ret = null; foreach (string folder in SearchableDirectories) { + if (!Directory.Exists(folder)) + continue; + ret = tryLoad(Path.Combine(folder, filename)); if (ret != null) return ret; - foreach (var childFolder in Directory.GetDirectories(folder)) + foreach (var childFolder in Directory.GetDirectories(folder, "*", SearchOption.AllDirectories)) { ret = tryLoad(Path.Combine(childFolder, filename)); if (ret != null) diff --git a/MelonLoader/MelonLoader.Shared/Fixes/UnhandledException.cs b/MelonLoader/MelonLoader.Shared/Fixes/UnhandledException.cs index 66a27486..1899af94 100644 --- a/MelonLoader/MelonLoader.Shared/Fixes/UnhandledException.cs +++ b/MelonLoader/MelonLoader.Shared/Fixes/UnhandledException.cs @@ -3,9 +3,9 @@ namespace MelonLoader.Fixes { - public static class UnhandledException + internal static class UnhandledException { - public static void Install(AppDomain domain) => + internal static void Install(AppDomain domain) => domain.UnhandledException += (sender, args) => MelonLogger.Error((args.ExceptionObject as Exception).ToString()); diff --git a/MelonLoader/MelonLoader.Shared/MelonLoader.Shared.csproj b/MelonLoader/MelonLoader.Shared/MelonLoader.Shared.csproj index af0d860c..362b26ab 100644 --- a/MelonLoader/MelonLoader.Shared/MelonLoader.Shared.csproj +++ b/MelonLoader/MelonLoader.Shared/MelonLoader.Shared.csproj @@ -21,7 +21,7 @@ https://github.com/LavaGang/MelonLoader Lava Gang Lava Gang - Copyright (c) 2023 Lava Gang + Copyright (c) 2020 - 2024 Lava Gang The World's First Universal Mod Loader, expandable to work on any game engine. diff --git a/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs b/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs index 3dac3ecd..8dd7825f 100644 --- a/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs +++ b/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs @@ -63,8 +63,60 @@ public static IntPtr ToAnsiPointer(this string str) #endregion + #region Enum + + /// + /// From: http://www.sambeauvois.be/blog/2011/08/enum-hasflag-method-extension-for-4-0-framework/ + /// A FX 3.5 way to mimic the FX4 "HasFlag" method. + /// + /// The tested enum. + /// The value to test. + /// True if the flag is set. Otherwise false. + public static bool HasFlag(this Enum variable, Enum value) + { + // check if from the same type. + if (variable.GetType() != value.GetType()) + throw new ArgumentException("The checked flag is not from the same type as the checked variable."); + + ulong num = Convert.ToUInt64(value); + ulong num2 = Convert.ToUInt64(variable); + return (num2 & num) == num; + } + + #endregion + #region Color + private static Dictionary DrawingColorDict = new Dictionary + { + { Color.Black, ConsoleColor.Black }, + { Color.DarkBlue, ConsoleColor.DarkBlue }, + { Color.DarkGreen, ConsoleColor.DarkGreen }, + { Color.DarkCyan, ConsoleColor.DarkCyan }, + { Color.DarkRed, ConsoleColor.DarkRed }, + { Color.DarkMagenta, ConsoleColor.DarkMagenta }, + { Color.Yellow, ConsoleColor.Yellow }, + { Color.LightGray, ConsoleColor.Gray }, + { Color.DarkGray, ConsoleColor.DarkGray }, + { Color.CornflowerBlue, ConsoleColor.Blue } , + { Color.LimeGreen, ConsoleColor.Green }, + { Color.Cyan, ConsoleColor.Cyan }, + { Color.IndianRed, ConsoleColor.Red }, + { Color.Magenta, ConsoleColor.Magenta }, + { Color.White, ConsoleColor.White }, + }; + + public static ConsoleColor ToConsoleColor(this Color color) + { + if (!DrawingColorDict.ContainsKey(color)) + return MelonUtils.DefaultTextConsoleColor; + return DrawingColorDict[color]; + } + + #endregion + + #region ConsoleColor + private static Dictionary ConsoleColorDict = new Dictionary { { ConsoleColor.Black, Color.Black }, @@ -85,25 +137,6 @@ public static IntPtr ToAnsiPointer(this string str) { ConsoleColor.White, Color.White }, }; - private static Dictionary DrawingColorDict = new Dictionary - { - { Color.Black, ConsoleColor.Black }, - { Color.DarkBlue, ConsoleColor.DarkBlue }, - { Color.DarkGreen, ConsoleColor.DarkGreen }, - { Color.DarkCyan, ConsoleColor.DarkCyan }, - { Color.DarkRed, ConsoleColor.DarkRed }, - { Color.DarkMagenta, ConsoleColor.DarkMagenta }, - { Color.Yellow, ConsoleColor.Yellow }, - { Color.LightGray, ConsoleColor.Gray }, - { Color.DarkGray, ConsoleColor.DarkGray }, - { Color.CornflowerBlue, ConsoleColor.Blue } , - { Color.LimeGreen, ConsoleColor.Green }, - { Color.Cyan, ConsoleColor.Cyan }, - { Color.IndianRed, ConsoleColor.Red }, - { Color.Magenta, ConsoleColor.Magenta }, - { Color.White, ConsoleColor.White }, - }; - public static Color ToDrawingColor(this ConsoleColor color) { if (!ConsoleColorDict.ContainsKey(color)) @@ -111,13 +144,6 @@ public static Color ToDrawingColor(this ConsoleColor color) return ConsoleColorDict[color]; } - public static ConsoleColor ToConsoleColor(this Color color) - { - if (!DrawingColorDict.ContainsKey(color)) - return MelonUtils.DefaultTextConsoleColor; - return DrawingColorDict[color]; - } - #endregion } } diff --git a/MelonLoader/MelonLoader.Shared/Utils/MelonLogger.cs b/MelonLoader/MelonLoader.Shared/Utils/MelonLogger.cs index 72bb4ef7..41c1c40a 100644 --- a/MelonLoader/MelonLoader.Shared/Utils/MelonLogger.cs +++ b/MelonLoader/MelonLoader.Shared/Utils/MelonLogger.cs @@ -161,7 +161,7 @@ internal static void Internal_Warning(string namesection, string txt) internal static void Internal_Error(string namesection, string txt) => Internal_Msg(Color.IndianRed, Color.IndianRed, namesection, txt); - internal static void WriteSpacer() + public static void WriteSpacer() { BootstrapInterop.NativeWriteLogToFile(""); Console.WriteLine(); diff --git a/MelonLoader/MelonLoader.sln b/MelonLoader/MelonLoader.sln index 997110fb..199163e7 100644 --- a/MelonLoader/MelonLoader.sln +++ b/MelonLoader/MelonLoader.sln @@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CoreCLR", "CoreCLR", "{40E3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreCLR.Bootstrap", "Modules\CoreCLR\CoreCLR.Bootstrap\CoreCLR.Bootstrap.csproj", "{0950C2EC-9116-4A7A-B5CF-A6C24CB90689}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Unity.Shared", "Modules\Unity\Unity.Shared\Unity.Shared.csproj", "{AAB39E01-B118-4481-9796-036E92B857A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +69,10 @@ Global {0950C2EC-9116-4A7A-B5CF-A6C24CB90689}.Debug|Any CPU.Build.0 = Debug|Any CPU {0950C2EC-9116-4A7A-B5CF-A6C24CB90689}.Release|Any CPU.ActiveCfg = Release|Any CPU {0950C2EC-9116-4A7A-B5CF-A6C24CB90689}.Release|Any CPU.Build.0 = Release|Any CPU + {AAB39E01-B118-4481-9796-036E92B857A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAB39E01-B118-4481-9796-036E92B857A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAB39E01-B118-4481-9796-036E92B857A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAB39E01-B118-4481-9796-036E92B857A0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,6 +87,7 @@ Global {5C34E23A-F841-460C-BD45-3EE1ED4E15F8} = {879B734F-9F94-4811-BA4A-28160BD525AA} {40E3E955-2FEE-4C38-BE89-76F61594DB4A} = {716CE800-E309-4104-ACA3-867BB8BF4FB0} {0950C2EC-9116-4A7A-B5CF-A6C24CB90689} = {40E3E955-2FEE-4C38-BE89-76F61594DB4A} + {AAB39E01-B118-4481-9796-036E92B857A0} = {3E7F5C09-221B-4492-9BA0-2D1ED9BEC58C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4490CB48-16CD-4D0A-858D-1484CB9DED02} diff --git a/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs b/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs index e2002f6f..7781fc9f 100644 --- a/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs +++ b/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs @@ -2,13 +2,17 @@ using MelonLoader.Interfaces; using MelonLoader.Utils; using MelonLoader.Mono.Bootstrap; -using System; using System.Collections.Generic; +using MelonLoader.Unity.Utils; +using System.Drawing; +using MelonLoader.Fixes; namespace MelonLoader.Unity { public class Bootstrap : IBootstrapModule { + private static readonly string GameDataPath = $"{Path.Combine(MelonEnvironment.GameRootDirectory, MelonEnvironment.GameExecutableName)}_Data"; + public string EngineName => "Unity"; /// @@ -27,10 +31,18 @@ public bool IsMyEngine } } - internal static string GameDataPath { get; private set; } = $"{Path.Combine(MelonEnvironment.GameRootDirectory, MelonEnvironment.GameExecutableName)}_Data"; - public void Startup() { + // Read Game Info + UnityEnvironment.Initialize(GameDataPath); + + // Log the Information + //BootstrapInterop.SetDefaultConsoleTitleWithGameName(GameName, GameVersion); + MelonLogger.Msg($"Engine Version: {UnityEnvironment.EngineVersionString}"); + MelonLogger.Msg($"Game Name: {UnityEnvironment.GameName}"); + MelonLogger.Msg($"Game Developer: {UnityEnvironment.GameDeveloper}"); + MelonLogger.Msg($"Game Version: {UnityEnvironment.GameVersion}"); + // Get GameAssembly Name string gameAssemblyName = "GameAssembly"; if (MelonUtils.IsUnix) @@ -45,7 +57,7 @@ public void Startup() if (File.Exists(gameAssemblyPath)) { // Start Il2Cpp Support - MelonLogger.Msg("Engine Variant: Il2Cpp"); + MelonLogger.Msg("Runtime Variant: Il2Cpp"); //Il2CppLoader.Startup(gameAssemblyPath); } else @@ -56,7 +68,7 @@ public void Startup() MelonAssertion.ThrowInternalFailure("Failed to get Mono Runtime Info!"); else { - MelonLogger.Msg($"Engine Variant: {runtimeInfo.VariantName}"); + MelonLogger.Msg($"Runtime Variant: {runtimeInfo.VariantName}"); MonoLoader.Startup(runtimeInfo); } } @@ -158,7 +170,6 @@ internal static MonoRuntimeInfo GetMonoRuntimeInfo() } } - // Return Nothing return null; } diff --git a/MelonLoader/Modules/Unity/Unity.Bootstrap/Unity.Bootstrap.csproj b/MelonLoader/Modules/Unity/Unity.Bootstrap/Unity.Bootstrap.csproj index 606e3c29..327317d6 100644 --- a/MelonLoader/Modules/Unity/Unity.Bootstrap/Unity.Bootstrap.csproj +++ b/MelonLoader/Modules/Unity/Unity.Bootstrap/Unity.Bootstrap.csproj @@ -18,5 +18,10 @@ all False + + False + all + False + \ No newline at end of file diff --git a/MelonLoader/Modules/Unity/Unity.Shared/Resources/classdata.tpk b/MelonLoader/Modules/Unity/Unity.Shared/Resources/classdata.tpk new file mode 100644 index 00000000..aeafd0ac Binary files /dev/null and b/MelonLoader/Modules/Unity/Unity.Shared/Resources/classdata.tpk differ diff --git a/MelonLoader/Modules/Unity/Unity.Shared/Unity.Shared.csproj b/MelonLoader/Modules/Unity/Unity.Shared/Unity.Shared.csproj new file mode 100644 index 00000000..3747b0e0 --- /dev/null +++ b/MelonLoader/Modules/Unity/Unity.Shared/Unity.Shared.csproj @@ -0,0 +1,26 @@ + + + MelonLoader.Unity + net35;netstandard2.1;net6 + Latest + $(SolutionDir)Output\$(Configuration)\MelonLoader\Modules\Unity\ + true + embedded + MelonLoader.Unity.Shared + + + + + + + + + + + + + + False + + + \ No newline at end of file diff --git a/MelonLoader/Modules/Unity/Unity.Shared/Utils/UnityEnvironment.cs b/MelonLoader/Modules/Unity/Unity.Shared/Utils/UnityEnvironment.cs new file mode 100644 index 00000000..479a0e36 --- /dev/null +++ b/MelonLoader/Modules/Unity/Unity.Shared/Utils/UnityEnvironment.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using AssetsTools.NET; +using AssetsTools.NET.Extra; +using UnityVersion = AssetRipper.Primitives.UnityVersion; +using MelonLoader.Utils; + +namespace MelonLoader.Unity.Utils +{ + public static class UnityEnvironment + { + private const string _defaultInfo = "UNKNOWN"; + private static bool _isInitialized; + + public static UnityVersion EngineVersion { get; private set; } = UnityVersion.MinVersion; + public static string EngineVersionString { get; private set; } = UnityVersion.MinVersion.ToString(); + + public static string GameName { get; private set; } + public static string GameDeveloper { get; private set; } + public static string GameVersion { get; private set; } + + public static void Initialize(string gameDataPath) + { + // Check if already Initialized + if (_isInitialized) + return; + _isInitialized = true; + + //if (!string.IsNullOrEmpty(MelonLaunchOptions.Core.UnityVersion)) + //{ + // try { EngineVersion = UnityVersion.Parse(MelonLaunchOptions.Core.UnityVersion); } + // catch (Exception ex) + // { + // EngineVersion = UnityVersion.MinVersion; + // if (MelonDebug.IsEnabled()) + // MelonLogger.Error(ex); + // } + //} + + AssetsManager assetsManager = new AssetsManager(); + ReadGameInfo(assetsManager, gameDataPath); + assetsManager.UnloadAll(); + + if (string.IsNullOrEmpty(GameDeveloper) + || string.IsNullOrEmpty(GameName)) + ReadGameInfoFallback(gameDataPath); + + if (EngineVersion == UnityVersion.MinVersion) + { + try { EngineVersion = ReadVersionFallback(gameDataPath); } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } + } + + if (string.IsNullOrEmpty(GameDeveloper)) + GameDeveloper = _defaultInfo; + if (string.IsNullOrEmpty(GameName)) + GameName = _defaultInfo; + if (string.IsNullOrEmpty(GameVersion)) + GameVersion = _defaultInfo; + + EngineVersionString = EngineVersion.ToString(); + } + + private static void ReadGameInfo(AssetsManager assetsManager, string gameDataPath) + { + AssetsFileInstance instance = null; + try + { + string bundlePath = Path.Combine(gameDataPath, "globalgamemanagers"); + if (!File.Exists(bundlePath)) + bundlePath = Path.Combine(gameDataPath, "mainData"); + + if (!File.Exists(bundlePath)) + { + bundlePath = Path.Combine(gameDataPath, "data.unity3d"); + if (!File.Exists(bundlePath)) + return; + + BundleFileInstance bundleFile = assetsManager.LoadBundleFile(bundlePath); + instance = assetsManager.LoadAssetsFileFromBundle(bundleFile, "globalgamemanagers"); + } + else + instance = assetsManager.LoadAssetsFile(bundlePath, true); + if (instance == null) + return; + + assetsManager.LoadIncludedClassPackage(); + + if (!instance.file.Metadata.TypeTreeEnabled) + assetsManager.LoadClassDatabaseFromPackage(instance.file.Metadata.UnityVersion); + + if (EngineVersion == UnityVersion.MinVersion) + EngineVersion = UnityVersion.Parse(instance.file.Metadata.UnityVersion); + + List assetFiles = instance.file.GetAssetsOfType(AssetClassID.PlayerSettings); + if (assetFiles.Count > 0) + { + AssetFileInfo playerSettings = assetFiles.First(); + + AssetTypeValueField playerSettings_baseField = assetsManager.GetBaseField(instance, playerSettings); + if (playerSettings_baseField != null) + { + AssetTypeValueField bundleVersion = playerSettings_baseField.Get("bundleVersion"); + if (bundleVersion != null) + GameVersion = bundleVersion.AsString; + + AssetTypeValueField companyName = playerSettings_baseField.Get("companyName"); + if (companyName != null) + GameDeveloper = companyName.AsString; + + AssetTypeValueField productName = playerSettings_baseField.Get("productName"); + if (productName != null) + GameName = productName.AsString; + } + } + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + //MelonLogger.Error("Failed to Initialize Assets Manager!"); + } + if (instance != null) + instance.file.Close(); + } + + private static void ReadGameInfoFallback(string gameDataPath) + { + try + { + string appInfoFilePath = Path.Combine(gameDataPath, "app.info"); + if (!File.Exists(appInfoFilePath)) + return; + + string[] filestr = File.ReadAllLines(appInfoFilePath); + if ((filestr == null) || (filestr.Length < 2)) + return; + + if (string.IsNullOrEmpty(GameDeveloper) && !string.IsNullOrEmpty(filestr[0])) + GameDeveloper = filestr[0]; + + if (string.IsNullOrEmpty(GameName) && !string.IsNullOrEmpty(filestr[1])) + GameName = filestr[1]; + + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } + } + + private static UnityVersion ReadVersionFallback(string gameDataPath) + { + + try + { + var globalgamemanagersPath = Path.Combine(gameDataPath, "globalgamemanagers"); + if (File.Exists(globalgamemanagersPath)) + return GetVersionFromGlobalGameManagers(File.ReadAllBytes(globalgamemanagersPath)); + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } + + try + { + var dataPath = Path.Combine(gameDataPath, "data.unity3d"); + if (File.Exists(dataPath)) + return GetVersionFromDataUnity3D(File.OpenRead(dataPath)); + } + catch (Exception ex) + { + if (MelonDebug.IsEnabled()) + MelonLogger.Error(ex); + } + + if (MelonUtils.IsWindows) + { + string unityPlayerPath = Path.Combine(MelonEnvironment.GameRootDirectory, "UnityPlayer.dll"); + if (!File.Exists(unityPlayerPath)) + unityPlayerPath = MelonEnvironment.GameExecutablePath; + + var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); + return new UnityVersion((ushort)unityVer.FileMajorPart, (ushort)unityVer.FileMinorPart, (ushort)unityVer.FileBuildPart); + } + + return default; + } + + private static UnityVersion GetVersionFromGlobalGameManagers(byte[] ggmBytes) + { + var verString = new StringBuilder(); + var idx = 0x14; + while (ggmBytes[idx] != 0) + { + verString.Append(Convert.ToChar(ggmBytes[idx])); + idx++; + } + + Regex UnityVersionRegex = new Regex(@"^[0-9]+\.[0-9]+\.[0-9]+[abcfx][0-9]+$", RegexOptions.Compiled); + string unityVer = verString.ToString(); + if (!UnityVersionRegex.IsMatch(unityVer)) + { + idx = 0x30; + verString = new StringBuilder(); + while (ggmBytes[idx] != 0) + { + verString.Append(Convert.ToChar(ggmBytes[idx])); + idx++; + } + + unityVer = verString.ToString().Trim(); + } + + return UnityVersion.Parse(unityVer); + } + + private static UnityVersion GetVersionFromDataUnity3D(Stream fileStream) + { + var verString = new StringBuilder(); + + if (fileStream.CanSeek) + fileStream.Seek(0x12, SeekOrigin.Begin); + else + { + if (fileStream.Read(new byte[0x12], 0, 0x12) != 0x12) + throw new("Failed to seek to 0x12 in data.unity3d"); + } + + while (true) + { + var read = fileStream.ReadByte(); + if (read == 0) + break; + verString.Append(Convert.ToChar(read)); + } + + return UnityVersion.Parse(verString.ToString().Trim()); + } + } +} diff --git a/MelonLoader/Modules/Unity/Unity.Shared/Utils/UnityExtensions.cs b/MelonLoader/Modules/Unity/Unity.Shared/Utils/UnityExtensions.cs new file mode 100644 index 00000000..f69dcdba --- /dev/null +++ b/MelonLoader/Modules/Unity/Unity.Shared/Utils/UnityExtensions.cs @@ -0,0 +1,20 @@ +using AssetsTools.NET; +using AssetsTools.NET.Extra; + +namespace MelonLoader.Unity.Utils +{ + public static class UnityExtensions + { + #region AssetsManager + + public static ClassPackageFile LoadIncludedClassPackage(this AssetsManager assetsManager) + { + ClassPackageFile classPackage = null; + using (var stream = typeof(UnityExtensions).Assembly.GetManifestResourceStream("MelonLoader.Unity.Resources.classdata.tpk")) + classPackage = assetsManager.LoadClassPackage(stream); + return classPackage; + } + + #endregion + } +}