using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Mono.Cecil;
using MonoMod.Utils;
namespace BepInEx
{
///
/// Generic helper properties and methods.
///
public static class Utility
{
private static bool? sreEnabled;
///
/// Whether current Common Language Runtime supports dynamic method generation using namespace.
///
public static bool CLRSupportsDynamicAssemblies => CheckSRE();
private static bool CheckSRE()
{
try
{
if (sreEnabled.HasValue)
return sreEnabled.Value;
// ReSharper disable once AssignNullToNotNullAttribute
_ = new CustomAttributeBuilder(null, new object[0]);
}
catch (PlatformNotSupportedException)
{
sreEnabled = false;
return sreEnabled.Value;
}
catch (ArgumentNullException)
{
// Suppress ArgumentNullException
}
sreEnabled = true;
return sreEnabled.Value;
}
///
/// Try to perform an action.
///
/// Action to perform.
/// Possible exception that gets returned.
/// True, if action succeeded, false if an exception occured.
public static bool TryDo(Action action, out Exception exception)
{
exception = null;
try
{
action();
return true;
}
catch (Exception e)
{
exception = e;
return false;
}
}
///
/// Combines multiple paths together, as the specific method is not available in .NET 3.5.
///
/// The multiple paths to combine together.
/// A combined path.
public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
///
/// Returns the parent directory of a path, optionally specifying the amount of levels.
///
/// The path to get the parent directory of.
/// The amount of levels to traverse. Defaults to 1
/// The parent directory.
public static string ParentDirectory(string path, int levels = 1)
{
for (int i = 0; i < levels; i++)
path = Path.GetDirectoryName(path);
return path;
}
///
/// Tries to parse a bool, with a default value if unable to parse.
///
/// The string to parse
/// The value to return if parsing is unsuccessful.
/// Boolean value of input if able to be parsed, otherwise default value.
public static bool SafeParseBool(string input, bool defaultValue = false)
{
return Boolean.TryParse(input, out bool result) ? result : defaultValue;
}
///
/// Converts a file path into a UnityEngine.WWW format.
///
/// The file path to convert.
/// A converted file path.
public static string ConvertToWWWFormat(string path)
{
return $"file://{path.Replace('\\', '/')}";
}
///
/// Indicates whether a specified string is null, empty, or consists only of white-space characters.
///
/// The string to test.
/// True if the value parameter is null or empty, or if value consists exclusively of white-space characters.
public static bool IsNullOrWhiteSpace(this string self)
{
return self == null || self.All(Char.IsWhiteSpace);
}
///
/// Sorts a given dependency graph using a direct toposort, reporting possible cyclic dependencies.
///
/// Nodes to sort
/// Function that maps a node to a collection of its dependencies.
/// Type of the node in a dependency graph.
/// Collection of nodes sorted in the order of least dependencies to the most.
/// Thrown when a cyclic dependency occurs.
public static IEnumerable TopologicalSort(IEnumerable nodes, Func> dependencySelector)
{
List sorted_list = new List();
HashSet visited = new HashSet();
HashSet sorted = new HashSet();
foreach (TNode input in nodes)
{
Stack currentStack = new Stack();
if (!Visit(input, currentStack))
{
throw new Exception("Cyclic Dependency:\r\n" + currentStack.Select(x => $" - {x}") //append dashes
.Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
}
}
return sorted_list;
bool Visit(TNode node, Stack stack)
{
if (visited.Contains(node))
{
if (!sorted.Contains(node))
{
return false;
}
}
else
{
visited.Add(node);
stack.Push(node);
foreach (var dep in dependencySelector(node))
if (!Visit(dep, stack))
return false;
sorted.Add(node);
sorted_list.Add(node);
stack.Pop();
}
return true;
}
}
///
/// Try to resolve and load the given assembly DLL.
///
/// Name of the assembly, of the type .
/// Directory to search the assembly from.
/// The loaded assembly.
/// True, if the assembly was found and loaded. Otherwise, false.
private static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, Func loader, out T assembly) where T : class
{
assembly = null;
var potentialDirectories = new List { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
foreach (string subDirectory in potentialDirectories)
{
string[] potentialPaths = new[]
{
$"{assemblyName.Name}.dll",
$"{assemblyName.Name}.exe"
};
foreach (var potentialPath in potentialPaths)
{
string path = Path.Combine(subDirectory, potentialPath);
if (!File.Exists(path))
continue;
try
{
assembly = loader(path);
}
catch (Exception)
{
continue;
}
return true;
}
}
return false;
}
///
/// Checks whether a given cecil type definition is a subtype of a provided type.
///
/// Cecil type definition
/// Type to check against
/// Whether the given cecil type is a subtype of the type.
public static bool IsSubtypeOf(this TypeDefinition self, Type td)
{
if (self.FullName == td.FullName)
return true;
return self.FullName != "System.Object" && (self.BaseType?.Resolve()?.IsSubtypeOf(td) ?? false);
}
///
/// Try to resolve and load the given assembly DLL.
///
/// Name of the assembly, of the type .
/// Directory to search the assembly from.
/// The loaded assembly.
/// True, if the assembly was found and loaded. Otherwise, false.
public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
{
return TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
}
///
/// Try to resolve and load the given assembly DLL.
///
/// Name of the assembly, of the type .
/// Directory to search the assembly from.
/// Reader parameters that contain possible custom assembly resolver.
/// The loaded assembly.
/// True, if the assembly was found and loaded. Otherwise, false.
public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, ReaderParameters readerParameters, out AssemblyDefinition assembly)
{
return TryResolveDllAssembly(assemblyName, directory, s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly);
}
///
/// Tries to create a file with the given name
///
/// Path of the file to create
/// File open mode
/// Resulting filestream
/// File access options
/// File share options
///
public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.Read)
{
try
{
fileStream = new FileStream(path, mode, access, share);
return true;
}
catch (IOException)
{
fileStream = null;
return false;
}
}
public static IEnumerable EnumerateAllMethods(this TypeDefinition type)
{
var currentType = type;
while (currentType != null)
{
foreach (var method in currentType.Methods)
yield return method;
currentType = currentType.BaseType?.Resolve();
}
}
public static string ByteArrayToString(byte[] data)
{
StringBuilder builder = new StringBuilder(data.Length * 2);
foreach (byte b in data)
builder.AppendFormat("{0:x2}", b);
return builder.ToString();
}
///
/// Try to parse given string as an assembly name
///
/// Fully qualified assembly name
/// Resulting instance
/// true, if parsing was successful, otherwise false
///
/// On some versions of mono, using fails because it runs on unmanaged side
/// which has problems with encoding.
/// Using solves this by doing parsing on managed side instead.
///
public static bool TryParseAssemblyName(string fullName, out AssemblyName assemblyName)
{
try
{
assemblyName = new AssemblyName(fullName);
return true;
}
catch (Exception)
{
assemblyName = null;
return false;
}
}
}
}