diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs index 75ba1d8d..6c3fc030 100644 --- a/IPA.Injector/Injector.cs +++ b/IPA.Injector/Injector.cs @@ -142,9 +142,9 @@ namespace IPA.Injector var sw = Stopwatch.StartNew(); var cAsmName = Assembly.GetExecutingAssembly().GetName(); - var managedPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var managedPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - var dataDir = new DirectoryInfo(managedPath).Parent.Name; + var dataDir = new DirectoryInfo(managedPath).Parent!.Name; var gameName = dataDir.Substring(0, dataDir.Length - 5); Logging.Logger.Injector.Debug("Finding backup"); @@ -153,145 +153,141 @@ namespace IPA.Injector if (bkp == null) Logging.Logger.Injector.Warn("No backup found! Was BSIPA installed using the installer?"); + // TODO: Investigate if this ever worked properly. + // this is a critical section because if you exit in here, assembly can die + using var critSec = CriticalSection.ExecuteSection(); + + var readerParameters = new ReaderParameters + { + ReadWrite = false, + InMemory = true, + ReadingMode = ReadingMode.Immediate + }; + Logging.Logger.Injector.Debug("Ensuring patch on UnityEngine.CoreModule exists"); #region Insert patch into UnityEngine.CoreModule.dll - { - var unityPath = Path.Combine(managedPath, - "UnityEngine.CoreModule.dll"); + var unityPath = Path.Combine(managedPath, "UnityEngine.CoreModule.dll"); - // this is a critical section because if you exit in here, CoreModule can die - using var critSec = CriticalSection.ExecuteSection(); + using var unityAsmDef = AssemblyDefinition.ReadAssembly(unityPath, readerParameters); + var unityModDef = unityAsmDef.MainModule; - using var unityAsmDef = AssemblyDefinition.ReadAssembly(unityPath, new ReaderParameters - { - ReadWrite = false, - InMemory = true, - ReadingMode = ReadingMode.Immediate - }); - var unityModDef = unityAsmDef.MainModule; - - bool modified = false; - foreach (var asmref in unityModDef.AssemblyReferences) + bool modified = false; + foreach (var asmref in unityModDef.AssemblyReferences) + { + if (asmref.Name == cAsmName.Name) { - if (asmref.Name == cAsmName.Name) + if (asmref.Version != cAsmName.Version) { - if (asmref.Version != cAsmName.Version) - { - asmref.Version = cAsmName.Version; - modified = true; - } + asmref.Version = cAsmName.Version; + modified = true; } } + } - var application = unityModDef.GetType("UnityEngine", "Camera"); + var application = unityModDef.GetType("UnityEngine", "Camera"); - if (application == null) - { - Logging.Logger.Injector.Critical("UnityEngine.CoreModule doesn't have a definition for UnityEngine.Camera!" - + "Nothing to patch to get ourselves into the Unity run cycle!"); - goto endPatchCoreModule; - } + if (application == null) + { + Logging.Logger.Injector.Critical("UnityEngine.CoreModule doesn't have a definition for UnityEngine.Camera!" + + "Nothing to patch to get ourselves into the Unity run cycle!"); + goto endPatchCoreModule; + } - MethodDefinition? cctor = null; - foreach (var m in application.Methods) - if (m.IsRuntimeSpecialName && m.Name == ".cctor") - cctor = m; + MethodDefinition? cctor = null; + foreach (var m in application.Methods) + if (m.IsRuntimeSpecialName && m.Name == ".cctor") + cctor = m; - var cbs = unityModDef.ImportReference(((Action)CreateBootstrapper).Method); + var cbs = unityModDef.ImportReference(((Action)CreateBootstrapper).Method); - if (cctor == null) - { - cctor = new MethodDefinition(".cctor", - MethodAttributes.RTSpecialName | MethodAttributes.Static | MethodAttributes.SpecialName, - unityModDef.TypeSystem.Void); - application.Methods.Add(cctor); - modified = true; - - var ilp = cctor.Body.GetILProcessor(); - ilp.Emit(OpCodes.Call, cbs); - ilp.Emit(OpCodes.Ret); - } - else + if (cctor == null) + { + cctor = new MethodDefinition(".cctor", + MethodAttributes.RTSpecialName | MethodAttributes.Static | MethodAttributes.SpecialName, + unityModDef.TypeSystem.Void); + application.Methods.Add(cctor); + modified = true; + + var ilp = cctor.Body.GetILProcessor(); + ilp.Emit(OpCodes.Call, cbs); + ilp.Emit(OpCodes.Ret); + } + else + { + var ilp = cctor.Body.GetILProcessor(); + for (var i = 0; i < Math.Min(2, cctor.Body.Instructions.Count); i++) { - var ilp = cctor.Body.GetILProcessor(); - for (var i = 0; i < Math.Min(2, cctor.Body.Instructions.Count); i++) + var ins = cctor.Body.Instructions[i]; + switch (i) { - var ins = cctor.Body.Instructions[i]; - switch (i) - { - case 0 when ins.OpCode != OpCodes.Call: - ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs)); - modified = true; - break; + case 0 when ins.OpCode != OpCodes.Call: + ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs)); + modified = true; + break; - case 0: + case 0: + { + var methodRef = ins.Operand as MethodReference; + if (methodRef?.FullName != cbs.FullName) { - var methodRef = ins.Operand as MethodReference; - if (methodRef?.FullName != cbs.FullName) - { - ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs)); - modified = true; - } - - break; + ilp.Replace(ins, ilp.Create(OpCodes.Call, cbs)); + modified = true; } - case 1 when ins.OpCode != OpCodes.Ret: - ilp.Replace(ins, ilp.Create(OpCodes.Ret)); - modified = true; + break; - } + } + case 1 when ins.OpCode != OpCodes.Ret: + ilp.Replace(ins, ilp.Create(OpCodes.Ret)); + modified = true; + break; } } + } - if (modified) - { - bkp?.Add(unityPath); - unityAsmDef.Write(unityPath); - } + if (modified) + { + string tempFilePath = Path.GetTempFileName(); + bkp?.Add(unityPath); + unityAsmDef.Write(tempFilePath); + File.Delete(unityPath); + File.Move(tempFilePath, unityPath); } endPatchCoreModule: #endregion Insert patch into UnityEngine.CoreModule.dll - bool isFirst = true; - foreach (var name in SelfConfig.GameAssemblies_) - { - var ascPath = Path.Combine(managedPath, name); - - using var execSec = CriticalSection.ExecuteSection(); - #if BeatSaber - if (isFirst) - { - try - { - Logging.Logger.Injector.Debug("Applying anti-yeet patch"); + Logging.Logger.Injector.Debug("Ensuring anti-yeet patch exists"); - using var ascAsmDef = AssemblyDefinition.ReadAssembly(ascPath, new ReaderParameters - { - ReadWrite = false, - InMemory = true, - ReadingMode = ReadingMode.Immediate - }); - var ascModDef = ascAsmDef.MainModule; + var name = SelfConfig.GameAssemblies_.FirstOrDefault() ?? SelfConfig.GetDefaultGameAssemblies().First(); + var ascPath = Path.Combine(managedPath, name); - var deleter = ascModDef.GetType("IPAPluginsDirDeleter"); - deleter.Methods.Clear(); // delete all methods + try + { + using var ascAsmDef = AssemblyDefinition.ReadAssembly(ascPath, readerParameters); + var ascModDef = ascAsmDef.MainModule; - ascAsmDef.Write(ascPath); + var deleter = ascModDef.GetType("IPAPluginsDirDeleter"); - isFirst = false; - } - catch (Exception e) - { - Logging.Logger.Injector.Warn($"Could not apply anti-yeet patch to {ascPath}"); - if (SelfConfig.Debug_.ShowHandledErrorStackTraces_) - Logging.Logger.Injector.Warn(e); - } + if (deleter.Methods.Count > 0) + { + deleter.Methods.Clear(); // delete all methods + + string tempFilePath = Path.GetTempFileName(); + bkp?.Add(ascPath); + ascAsmDef.Write(tempFilePath); + File.Delete(ascPath); + File.Move(tempFilePath, ascPath); } -#endif } + catch (Exception e) + { + Logging.Logger.Injector.Warn($"Could not apply anti-yeet patch to {ascPath}"); + if (SelfConfig.Debug_.ShowHandledErrorStackTraces_) + Logging.Logger.Injector.Warn(e); + } +#endif sw.Stop(); Logging.Logger.Injector.Info($"Installing bootstrapper took {sw.Elapsed}"); @@ -304,7 +300,7 @@ namespace IPA.Injector if (bootstrapped) return; bootstrapped = true; - Application.logMessageReceived += delegate (string condition, string stackTrace, LogType type) + Application.logMessageReceivedThreaded += delegate (string condition, string stackTrace, LogType type) { var level = UnityLogRedirector.LogTypeToLevel(type); UnityLogProvider.UnityLogger.Log(level, $"{condition}"); @@ -333,4 +329,4 @@ namespace IPA.Injector _ = PluginComponent.Create(); } } -} \ No newline at end of file +} diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs index 2d69d585..0af170ad 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs @@ -1,17 +1,8 @@ #nullable enable using IPA.Config.Data; -using IPA.Config.Stores.Attributes; -using IPA.Logging; using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using System.Reflection.Emit; -using System.Text; -using System.Threading; -using System.Threading.Tasks; #if NET3 using Net3_Proxy; using Array = Net3_Proxy.Array; @@ -46,12 +37,27 @@ namespace IPA.Config.Stores var endLabel = il.DefineLabel(); - // TODO: when we have a nullable, we need to save to a local to call methods if (member.IsNullable) { - il.Emit(OpCodes.Dup); + using var valueTypeLocal = GetLocal.Allocate(member.Type); + var passedNull = il.DefineLabel(); + + il.Emit(OpCodes.Stloc, valueTypeLocal); + il.Emit(OpCodes.Ldloca, valueTypeLocal); il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod()); - il.Emit(OpCodes.Brfalse, endLabel); + il.Emit(OpCodes.Brtrue, passedNull); + + if (member.ConversionType.IsValueType) + { + il.Emit(OpCodes.Ldloca, valueTypeLocal); + il.Emit(OpCodes.Initobj, member.Type); + } + il.Emit(OpCodes.Ldloc, valueTypeLocal); + il.Emit(OpCodes.Br, endLabel); + + il.MarkLabel(passedNull); + + il.Emit(OpCodes.Ldloca, valueTypeLocal); il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod()); } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs index 07904fce..18744fed 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs @@ -41,7 +41,7 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize); } - private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal, + private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal, Action thisarg, Action parentobj) { if (!member.IsNullable) throw new InvalidOperationException("EmitDeserializeNullable called for non-nullable!"); @@ -53,7 +53,7 @@ namespace IPA.Config.Stores // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType // after, top of stack will be thing to write to field - private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, LocalAllocator GetLocal, + private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, LocalAllocator GetLocal, Action thisarg, Action parentobj) { if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing @@ -96,14 +96,14 @@ namespace IPA.Config.Stores { using var mapLocal = GetLocal.Allocate(typeof(Map)); - using var resultLocal = GetLocal.Allocate(targetType); + using var resultLocal = GetLocal.Allocate(member.IsNullable ? member.Type : targetType); using var valueLocal = GetLocal.Allocate(typeof(Value)); var structure = ReadObjectMembers(targetType); if (!structure.Any()) { - Logger.Config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" + - $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members"); + Logger.Config.Warn($"Custom value type {targetType.FullName} (when compiling deserialization of" + + $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members, might need a converter"); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldloca, resultLocal); il.Emit(OpCodes.Initobj, targetType); @@ -112,7 +112,6 @@ namespace IPA.Config.Stores { il.Emit(OpCodes.Stloc, mapLocal); - // TODO: handle creating a nullable, when applicable EmitLoad(il, member, thisarg); il.Emit(OpCodes.Stloc, resultLocal); @@ -130,7 +129,7 @@ namespace IPA.Config.Stores } } - private static void EmitDeserializeStructure(ILGenerator il, IEnumerable structure, + private static void EmitDeserializeStructure(ILGenerator il, IEnumerable structure, LocalBuilder mapLocal, LocalBuilder valueLocal, LocalAllocator GetLocal, Action thisobj, Action parentobj) { @@ -204,7 +203,7 @@ namespace IPA.Config.Stores } // emit takes the value being deserialized, logs on error, leaves nothing on stack - private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action getValue, LocalAllocator GetLocal, + private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action getValue, LocalAllocator GetLocal, Action thisobj, Action parentobj) { var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType)); diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs index 7efa987a..fdb83d43 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs @@ -34,8 +34,8 @@ namespace IPA.Config.Stores public static IConfigStore Create(Type type) => Create(type, null); - private static readonly MethodInfo CreateGParent = - typeof(GeneratedStoreImpl).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null, + private static readonly MethodInfo CreateGParent = + typeof(GeneratedStoreImpl).GetMethod(nameof(Create), BindingFlags.NonPublic | BindingFlags.Static, null, CallingConventions.Any, new[] { typeof(IGeneratedStore) }, Array.Empty()); internal static T Create(IGeneratedStore? parent) where T : class => (T)Create(typeof(T), parent); @@ -72,7 +72,14 @@ namespace IPA.Config.Stores internal static void DebugSaveAssembly(string file) { - Assembly.Save(file); + try + { + Assembly.Save(file); + } + catch (Exception ex) + { + Logger.Config.Error(ex); + } } private static ModuleBuilder? module; diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs index 54595d76..b5a9040b 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs @@ -78,7 +78,7 @@ namespace IPA.Config.Stores if (converterAttr.UseDefaultConverterForType) converterAttr = new UseConverterAttribute(Converter.GetDefaultConverterType(member.Type)); if (converterAttr.UseDefaultConverterForType) - throw new InvalidOperationException("How did we get here?"); + throw new InvalidOperationException("How did we get here?"); member.Converter = converterAttr.ConverterType; member.IsGenericConverter = converterAttr.IsGenericConverter; @@ -175,7 +175,7 @@ namespace IPA.Config.Stores // only look at public/protected fields foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - if (field.IsPrivate) + if (field.IsPrivate || field.IsAssembly) continue; var smi = new SerializedMemberInfo diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs index 358bef88..af0d04ad 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs @@ -28,44 +28,58 @@ namespace IPA.Config.Stores ? GetLocal.Allocate(member.Type) : default; - if (member.IsNullable) - { - il.Emit(OpCodes.Stloc, valueTypeLocal.Local); - il.Emit(OpCodes.Ldloca, valueTypeLocal.Local); - } - var endSerialize = il.DefineLabel(); if (member.AllowNull) { var passedNull = il.DefineLabel(); - il.Emit(OpCodes.Dup); if (member.IsNullable) + { + il.Emit(OpCodes.Stloc, valueTypeLocal.Local); + il.Emit(OpCodes.Ldloca, valueTypeLocal.Local); il.Emit(OpCodes.Call, member.Nullable_HasValue.GetGetMethod()); + } il.Emit(OpCodes.Brtrue, passedNull); - il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Br, endSerialize); il.MarkLabel(passedNull); - } - if (member.IsNullable) - il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod()); + if (member.IsNullable) + { + if (!member.HasConverter) + { + il.Emit(OpCodes.Ldloca, valueTypeLocal.Local); + il.Emit(OpCodes.Call, member.Nullable_Value.GetGetMethod()); + } + } + else + { + EmitLoad(il, member, thisarg); + } + } var memberConversionType = member.ConversionType; var targetType = GetExpectedValueTypeForType(memberConversionType); if (member.HasConverter) { - using var stlocal = GetLocal.Allocate(memberConversionType); - using var valLocal = GetLocal.Allocate(typeof(Value)); + if (member.IsNullable) + { + il.BeginExceptionBlock(); + il.Emit(OpCodes.Ldsfld, member.ConverterField); + il.Emit(OpCodes.Ldloc, valueTypeLocal.Local); + } + else + { + using var stlocal = GetLocal.Allocate(memberConversionType); - il.Emit(OpCodes.Stloc, stlocal); - il.BeginExceptionBlock(); - il.Emit(OpCodes.Ldsfld, member.ConverterField); - il.Emit(OpCodes.Ldloc, stlocal); + il.Emit(OpCodes.Stloc, stlocal); + il.BeginExceptionBlock(); + il.Emit(OpCodes.Ldsfld, member.ConverterField); + il.Emit(OpCodes.Ldloc, stlocal); + } if (member.IsGenericConverter) { @@ -87,18 +101,18 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Call, toValue); } - il.Emit(OpCodes.Stloc, valLocal); + il.Emit(OpCodes.Stloc_1); il.BeginCatchBlock(typeof(Exception)); EmitWarnException(il, "Error serializing member using converter"); il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Stloc, valLocal); + il.Emit(OpCodes.Stloc_1); il.EndExceptionBlock(); - il.Emit(OpCodes.Ldloc, valLocal); + il.Emit(OpCodes.Ldloc_1); } else if (targetType == typeof(Text)) { // only happens when arg is a string or char var TextCreate = typeof(Value).GetMethod(nameof(Value.Text)); - if (member.Type == typeof(char)) + if (memberConversionType == typeof(char)) { var strFromChar = typeof(char).GetMethod(nameof(char.ToString), new[] { typeof(char) }); il.Emit(OpCodes.Call, strFromChar); @@ -113,13 +127,13 @@ namespace IPA.Config.Stores else if (targetType == typeof(Integer)) { var IntCreate = typeof(Value).GetMethod(nameof(Value.Integer)); - EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, member.Type); + EmitNumberConvertTo(il, IntCreate.GetParameters()[0].ParameterType, memberConversionType); il.Emit(OpCodes.Call, IntCreate); } else if (targetType == typeof(FloatingPoint)) { var FloatCreate = typeof(Value).GetMethod(nameof(Value.Float)); - EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, member.Type); + EmitNumberConvertTo(il, FloatCreate.GetParameters()[0].ParameterType, memberConversionType); il.Emit(OpCodes.Call, FloatCreate); } else if (targetType == typeof(List)) @@ -169,7 +183,7 @@ namespace IPA.Config.Stores if (!structure.Any()) { Logger.Config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" + - $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members"); + $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members, might need a converter"); il.Emit(OpCodes.Pop); } else diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index 04930963..f20d428d 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -598,7 +598,7 @@ namespace IPA.Loader Logger.Loader.Trace($"- Found already processed"); return true; } - if (metadataCache!.TryGetValue(id, out var plugin)) + if (metadataCache.TryGetValue(id, out var plugin)) { Logger.Loader.Trace($"- In metadata cache"); if (partial) @@ -609,11 +609,11 @@ namespace IPA.Loader disabled = !plugin.Enabled; meta = plugin.Meta; + ignored = false; if (!disabled) { try { - ignored = false; Resolve(plugin.Meta, ref disabled, out ignored); } catch (Exception e) @@ -715,8 +715,7 @@ namespace IPA.Loader if (depDisabled) { Logger.Loader.Warn($"Dependency '{id}' for '{plugin.Id}' disabled; disabling"); - disabledPlugins!.Add(plugin); - _ = disabledIds!.Add(plugin.Id); + disabledPlugins.Add(plugin); disabled = true; } @@ -770,7 +769,6 @@ namespace IPA.Loader && range.Matches(meta.HVersion) && !conflIgnored && !conflDisabled) // the conflict is only *actually* a problem if it is both not ignored and not disabled { - Logger.Loader.Warn($"Plugin '{plugin.Id}' conflicts with {meta.Id}@{meta.HVersion}; ignoring '{plugin.Id}'"); ignoredPlugins.Add(plugin, new(Reason.Conflict) { diff --git a/IPA.Loader/Utilities/AlmostVersion.cs b/IPA.Loader/Utilities/AlmostVersion.cs index a9527503..494090e9 100644 --- a/IPA.Loader/Utilities/AlmostVersion.cs +++ b/IPA.Loader/Utilities/AlmostVersion.cs @@ -10,7 +10,7 @@ using Version = Hive.Versioning.Version; namespace IPA.Utilities { /// - /// A type that wraps so that the string of the version is stored when the string is + /// A type that wraps so that the string of the version is stored when the string is /// not a valid . /// public class AlmostVersion : IComparable, IComparable, @@ -61,7 +61,7 @@ namespace IPA.Utilities public AlmostVersion(SVersion ver) : this(ver?.UnderlyingVersion ?? throw new ArgumentNullException(nameof(ver))) { } /// - /// Creates an from the version string in stored using + /// Creates an from the version string in stored using /// the storage mode specified in . /// /// the text to parse as an @@ -92,7 +92,13 @@ namespace IPA.Utilities if (mode == StoredAs.SemVer) { StorageMode = StoredAs.SemVer; +#if BeatSaber + var index = str.IndexOf('_'); + var versionString = index >= 0 ? str.Substring(0, index) : str; + var result = Version.TryParse(versionString, out var version); +#else var result = Version.TryParse(str, out var version); +#endif SemverValue = version; return result; } diff --git a/IPA/Program.cs b/IPA/Program.cs index ef524c67..f2f5f49d 100644 --- a/IPA/Program.cs +++ b/IPA/Program.cs @@ -72,7 +72,7 @@ namespace IPA } PatchContext? context = null; - + Assembly? AssemblyLibLoader(object? source, ResolveEventArgs e) { // ReSharper disable AccessToModifiedClosure @@ -329,8 +329,16 @@ namespace IPA Debug.Assert(targetFile.Directory != null, "targetFile.Directory != null"); targetFile.Directory?.Create(); - LineBack(); - ClearLine(); + try + { + LineBack(); + ClearLine(); + } + catch (Exception ex) + { + // Might throw IOException due to an invalid handle when accessing IsConsole from a MSBuild task. + } + Console.WriteLine(@"Copying {0}", targetFile.FullName); backup.Add(targetFile); _ = fi.CopyTo(targetFile.FullName, true); @@ -366,7 +374,7 @@ namespace IPA /// Encodes an argument for passing into a program /// /// The value_ that should be received by the program - /// The value_ which needs to be passed to the program for the original value_ + /// The value_ which needs to be passed to the program for the original value_ /// to come through public static string EncodeParameterArgument(string original) {