mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-20 01:46:13 +00:00
185 lines
6.3 KiB
C#
185 lines
6.3 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using BepInEx.ConsoleUtil;
|
|
using HarmonyLib;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using MonoMod.Utils;
|
|
using UnityInjector.ConsoleUtil;
|
|
|
|
namespace BepInEx;
|
|
|
|
internal class WindowsConsoleDriver : IConsoleDriver
|
|
{
|
|
// Apparently on some versions of Unity (e.g. 2018.4) using old mono causes crashes on game close if
|
|
// IntPtr overload is used for file streams (check #139).
|
|
// On the other hand, not all Unity games come with SafeFileHandle overload for FileStream
|
|
// As such, we're trying to use SafeFileHandle when it's available and go back to IntPtr overload if not available
|
|
private static readonly ConstructorInfo FileStreamCtor = new[]
|
|
{
|
|
AccessTools.Constructor(typeof(FileStream), new[] { typeof(SafeFileHandle), typeof(FileAccess) }),
|
|
AccessTools.Constructor(typeof(FileStream), new[] { typeof(IntPtr), typeof(FileAccess) })
|
|
}.FirstOrDefault(m => m != null);
|
|
|
|
private readonly Func<int> getWindowHeight = AccessTools
|
|
.PropertyGetter(typeof(Console), nameof(Console.WindowHeight))
|
|
?.CreateDelegate<Func<int>>();
|
|
|
|
private readonly Func<int> getWindowWidth = AccessTools
|
|
.PropertyGetter(typeof(Console), nameof(Console.WindowWidth))
|
|
?.CreateDelegate<Func<int>>();
|
|
|
|
private bool useManagedEncoder;
|
|
|
|
private int ConsoleWidth
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
return getWindowWidth?.Invoke() ?? 0;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private int ConsoleHeight
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
return getWindowHeight?.Invoke() ?? 0;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public TextWriter StandardOut { get; private set; }
|
|
public TextWriter ConsoleOut { get; private set; }
|
|
|
|
public bool ConsoleActive { get; private set; }
|
|
public bool ConsoleIsExternal => true;
|
|
|
|
public void Initialize(bool alreadyActive, bool useManagedEncoder)
|
|
{
|
|
ConsoleActive = alreadyActive;
|
|
this.useManagedEncoder = useManagedEncoder;
|
|
|
|
if (ConsoleActive)
|
|
{
|
|
// We're in a .NET framework / XNA environment; console *is* stdout
|
|
ConsoleOut = Console.Out;
|
|
StandardOut = new StreamWriter(Console.OpenStandardOutput());
|
|
}
|
|
else
|
|
{
|
|
StandardOut = Console.Out;
|
|
}
|
|
}
|
|
|
|
public void CreateConsole(uint codepage)
|
|
{
|
|
ConsoleWindow.Attach();
|
|
|
|
// Make sure of ConsoleEncoding helper class because on some Monos
|
|
// Encoding.GetEncoding throws NotImplementedException on most codepages
|
|
// NOTE: We don't set Console.OutputEncoding because it resets any existing Console.Out writers
|
|
if (!useManagedEncoder)
|
|
ConsoleEncoding.ConsoleCodePage = codepage;
|
|
|
|
// If stdout exists, write to it, otherwise make it the same as console out
|
|
// Not sure if this is needed? Does the original Console.Out still work?
|
|
var stdout = GetOutHandle();
|
|
if (stdout == IntPtr.Zero)
|
|
{
|
|
StandardOut = TextWriter.Null;
|
|
ConsoleOut = TextWriter.Null;
|
|
return;
|
|
}
|
|
|
|
var originalOutStream = OpenFileStream(stdout);
|
|
StandardOut = new StreamWriter(originalOutStream, Utility.UTF8NoBom)
|
|
{
|
|
AutoFlush = true
|
|
};
|
|
|
|
var consoleOutStream = OpenFileStream(ConsoleWindow.ConsoleOutHandle);
|
|
// Can't use Console.OutputEncoding because it can be null (i.e. not preference by user)
|
|
ConsoleOut = new StreamWriter(consoleOutStream,
|
|
useManagedEncoder ? Utility.UTF8NoBom : ConsoleEncoding.OutputEncoding)
|
|
{
|
|
AutoFlush = true
|
|
};
|
|
ConsoleActive = true;
|
|
|
|
if (ReflectionHelper.IsCore)
|
|
{
|
|
Console.SetOut(ConsoleOut);
|
|
}
|
|
}
|
|
|
|
public void PreventClose() => ConsoleWindow.PreventClose();
|
|
|
|
public void DetachConsole()
|
|
{
|
|
ConsoleWindow.Detach();
|
|
|
|
ConsoleOut.Close();
|
|
ConsoleOut = null;
|
|
|
|
ConsoleActive = false;
|
|
}
|
|
|
|
public void SetConsoleColor(ConsoleColor color)
|
|
{
|
|
SafeConsole.ForegroundColor = color;
|
|
Kon.ForegroundColor = color;
|
|
}
|
|
|
|
public void SetConsoleTitle(string title) => ConsoleWindow.Title = title;
|
|
|
|
private static Stream OpenFileStream(IntPtr handle)
|
|
{
|
|
if (ReflectionHelper.IsCore)
|
|
{
|
|
var windowsConsoleStreamType = Type.GetType("System.ConsolePal+WindowsConsoleStream, System.Console", true);
|
|
var constructor = AccessTools.Constructor(windowsConsoleStreamType,
|
|
new[] { typeof(IntPtr), typeof(FileAccess), typeof(bool) });
|
|
return (Stream)constructor.Invoke(new object[] { handle, FileAccess.Write, true });
|
|
}
|
|
|
|
var fileHandle = new SafeFileHandle(handle, false);
|
|
var ctorParams = AccessTools.ActualParameters(FileStreamCtor,
|
|
new object[]
|
|
{
|
|
fileHandle, fileHandle.DangerousGetHandle(),
|
|
FileAccess.Write
|
|
});
|
|
return (FileStream) Activator.CreateInstance(typeof(FileStream), ctorParams);
|
|
}
|
|
|
|
private IntPtr GetOutHandle()
|
|
{
|
|
switch (ConsoleManager.ConfigConsoleOutRedirectType.Value)
|
|
{
|
|
case ConsoleManager.ConsoleOutRedirectType.ConsoleOut:
|
|
return ConsoleWindow.ConsoleOutHandle;
|
|
case ConsoleManager.ConsoleOutRedirectType.StandardOut:
|
|
return ConsoleWindow.OriginalStdoutHandle;
|
|
case ConsoleManager.ConsoleOutRedirectType.Auto:
|
|
default:
|
|
return ConsoleWindow.OriginalStdoutHandle != IntPtr.Zero
|
|
? ConsoleWindow.OriginalStdoutHandle
|
|
: ConsoleWindow.ConsoleOutHandle;
|
|
}
|
|
}
|
|
}
|