Browse Source

Merge pull request #100 from Meivyn/patch-1

Bunch of fixes
pull/104/head
nike4613 3 months ago
committed by GitHub
parent
commit
b9a65ab25d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 202 additions and 168 deletions
  1. +104
    -108
      IPA.Injector/Injector.cs
  2. +18
    -12
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs
  3. +7
    -8
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs
  4. +10
    -3
      IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
  5. +2
    -2
      IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
  6. +38
    -24
      IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs
  7. +3
    -5
      IPA.Loader/Loader/PluginLoader.cs
  8. +8
    -2
      IPA.Loader/Utilities/AlmostVersion.cs
  9. +12
    -4
      IPA/Program.cs

+ 104
- 108
IPA.Injector/Injector.cs View File

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

+ 18
- 12
IPA.Loader/Config/Stores/GeneratedStoreImpl/Correction.cs View File

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


+ 7
- 8
IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs View File

@ -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<ILGenerator> thisarg, Action<ILGenerator> 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<ILGenerator> thisarg, Action<ILGenerator> 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<SerializedMemberInfo> structure,
private static void EmitDeserializeStructure(ILGenerator il, IEnumerable<SerializedMemberInfo> structure,
LocalBuilder mapLocal, LocalBuilder valueLocal,
LocalAllocator GetLocal, Action<ILGenerator> thisobj, Action<ILGenerator> 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<ILGenerator> getValue, LocalAllocator GetLocal,
private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, LocalAllocator GetLocal,
Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
{
var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));


+ 10
- 3
IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs View File

@ -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<ParameterModifier>());
internal static T Create<T>(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;


+ 2
- 2
IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs View File

@ -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


+ 38
- 24
IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs View File

@ -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


+ 3
- 5
IPA.Loader/Loader/PluginLoader.cs View File

@ -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)
{


+ 8
- 2
IPA.Loader/Utilities/AlmostVersion.cs View File

@ -10,7 +10,7 @@ using Version = Hive.Versioning.Version;
namespace IPA.Utilities
{
/// <summary>
/// A type that wraps <see cref="Version"/> so that the string of the version is stored when the string is
/// A type that wraps <see cref="Version"/> so that the string of the version is stored when the string is
/// not a valid <see cref="Version"/>.
/// </summary>
public class AlmostVersion : IComparable<AlmostVersion>, IComparable<Version>,
@ -61,7 +61,7 @@ namespace IPA.Utilities
public AlmostVersion(SVersion ver) : this(ver?.UnderlyingVersion ?? throw new ArgumentNullException(nameof(ver))) { }
/// <summary>
/// Creates an <see cref="AlmostVersion"/> from the version string in <paramref name="vertext"/> stored using
/// Creates an <see cref="AlmostVersion"/> from the version string in <paramref name="vertext"/> stored using
/// the storage mode specified in <paramref name="mode"/>.
/// </summary>
/// <param name="vertext">the text to parse as an <see cref="AlmostVersion"/></param>
@ -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;
}


+ 12
- 4
IPA/Program.cs View File

@ -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
/// </summary>
/// <param name="original">The value_ that should be received by the program</param>
/// <returns>The value_ which needs to be passed to the program for the original value_
/// <returns>The value_ which needs to be passed to the program for the original value_
/// to come through</returns>
public static string EncodeParameterArgument(string original)
{


Loading…
Cancel
Save