diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs index 7d338af7..6f51546c 100644 --- a/IPA.Injector/Injector.cs +++ b/IPA.Injector/Injector.cs @@ -45,8 +45,8 @@ namespace IPA.Injector _ = args; try { - if (Environment.GetCommandLineArgs().Contains("--verbose")) - WinConsole.Initialize(); + var arguments = Environment.GetCommandLineArgs(); + MaybeInitializeConsole(arguments); SetupLibraryLoading(); @@ -55,8 +55,8 @@ namespace IPA.Injector // this is weird, but it prevents Mono from having issues loading the type. // IMPORTANT: NO CALLS TO ANY LOGGER CAN HAPPEN BEFORE THIS var unused = StandardLogger.PrintFilter; - #region // Above hack explaination - /* + #region // Above hack explanation + /* * Due to an unknown bug in the version of Mono that Unity uses, if the first access to StandardLogger * is a call to a constructor, then Mono fails to load the type correctly. However, if the first access is to * the above static property (or maybe any, but I don't really know) it behaves as expected and works fine. @@ -65,7 +65,7 @@ namespace IPA.Injector Default.Debug("Initializing logger"); - SelfConfig.ReadCommandLine(Environment.GetCommandLineArgs()); + SelfConfig.ReadCommandLine(arguments); SelfConfig.Load(); DisabledConfig.Load(); @@ -102,6 +102,25 @@ namespace IPA.Injector } } + private static void MaybeInitializeConsole(string[] arguments) + { + var i = 0; + while (i < arguments.Length) + { + if (arguments[i++] == "--verbose") + { + if (i == arguments.Length) + { + WinConsole.Initialize(WinConsole.AttachParent); + return; + } + + WinConsole.Initialize(int.TryParse(arguments[i], out int processId) ? processId : WinConsole.AttachParent); + return; + } + } + } + private static void EnsureDirectories() { string path; @@ -256,7 +275,7 @@ namespace IPA.Injector using var ascModule = VirtualizedModule.Load(ascPath); ascModule.Virtualize(cAsmName, () => bkp?.Add(ascPath)); } - catch (Exception e) + catch (Exception e) { Logging.Logger.Injector.Error($"Could not virtualize {ascPath}"); if (SelfConfig.Debug_.ShowHandledErrorStackTraces_) diff --git a/IPA.Loader/Logging/ConsoleWindow.cs b/IPA.Loader/Logging/ConsoleWindow.cs index 89f07fb2..5284b201 100644 --- a/IPA.Loader/Logging/ConsoleWindow.cs +++ b/IPA.Loader/Logging/ConsoleWindow.cs @@ -22,14 +22,12 @@ namespace IPA.Logging internal static bool IsInitialized; - public static void Initialize(bool alwaysCreateNewConsole = false) + public static void Initialize(int processId, bool alwaysCreateNewConsole = false) { - bool consoleAttached = true; - if (alwaysCreateNewConsole - || (AttachConsole(AttachParent) == 0 - && Marshal.GetLastWin32Error() != ErrorAccessDenied)) + bool consoleAttached; + if (alwaysCreateNewConsole || !(consoleAttached = AttachConsole(processId))) { - consoleAttached = AllocConsole() != 0; + consoleAttached = AllocConsole(); } if (consoleAttached) @@ -39,7 +37,7 @@ namespace IPA.Logging } } - public static void InitializeStreams() + private static void InitializeStreams() { InitializeOutStream(); InitializeInStream(); @@ -62,7 +60,7 @@ namespace IPA.Logging if (!SetConsoleMode(handle, mode)) { UseVTEscapes = false; - Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " + + Console.Error.WriteLine("Could not enable VT100 escape code processing (maybe you're running an old Windows?): " + new Win32Exception(Marshal.GetLastWin32Error()).Message); } } @@ -87,18 +85,11 @@ namespace IPA.Logging private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode, FileAccess dotNetFileAccess, out SafeFileHandle handle) { - var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true); + var file = new SafeFileHandle(CreateFile(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OpenExisting, FileAttributeNormal, IntPtr.Zero), true); if (!file.IsInvalid) { handle = file; -#if NET4 var fs = new FileStream(file, dotNetFileAccess); -#elif NET3 -#pragma warning disable CS0618 - // this is marked obsolete, and shouldn't need to be used, but the constructor used in .NET 4 doesn't exist in Unity's mscorlib.dll - var fs = new FileStream(file.DangerousGetHandle(), dotNetFileAccess); -#pragma warning restore -#endif return fs; } @@ -106,43 +97,31 @@ namespace IPA.Logging return null; } -#region Win API Functions and Constants - [DllImport("kernel32.dll", - EntryPoint = "AllocConsole", - SetLastError = true, - CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall)] - private static extern int AllocConsole(); - - [DllImport("kernel32.dll", - EntryPoint = "AttachConsole", - SetLastError = true, - CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall)] - private static extern uint AttachConsole(uint dwProcessId); - - [DllImport("kernel32.dll", - EntryPoint = "CreateFileW", - SetLastError = true, - CharSet = CharSet.Unicode, - CallingConvention = CallingConvention.StdCall)] - private static extern IntPtr CreateFileW( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr lpSecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile - ); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + #region Win API Functions and Constants + + [DllImport("kernel32.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern bool AllocConsole(); + + [DllImport("kernel32.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern bool AttachConsole(int dwProcessId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, + IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [DllImport("kernel32.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] private static extern IntPtr GetStdHandle(int nStdHandle); private const uint EnableVTProcessing = 0x0004; @@ -153,10 +132,9 @@ namespace IPA.Logging private const uint FileShareWrite = 0x00000002; private const uint OpenExisting = 0x00000003; private const uint FileAttributeNormal = 0x80; - private const uint ErrorAccessDenied = 5; - - private const uint AttachParent = 0xFFFFFFFF; -#endregion + internal const int AttachParent = -1; + + #endregion } } \ No newline at end of file diff --git a/docs/articles/command-line.md b/docs/articles/command-line.md index ea3e846c..da9cce06 100644 --- a/docs/articles/command-line.md +++ b/docs/articles/command-line.md @@ -26,6 +26,16 @@ Here's a quick list of what they are and what they do. > Makes a console appear with log information at startup. > + > Optionally, an explicit process ID can be specified to start the game with an external console. This allows it to be + > launched via Steam without being interrupted by its "Allow game launch?" if launched directly from the `.exe`. + > + > Example for Beat Saber using PowerShell: + > + > ``` + > .\steam.exe -applaunch 620980 --verbose $PID + > ``` + > + > Do note that this isn't going to work from an elevated terminal. - `--debug` @@ -35,15 +45,13 @@ Here's a quick list of what they are and what they do. > This option also forces BSIPA to show all debug messages in the console, as well as where they were called. > > This overrides the config settings `Debug.ShowDebug` and `Debug.ShowCallSource`. - > - `--trace` - + > Enables trace level messages. By default, they do not ever enter the message queue, and thus cost almost nothing. > When this or the config option is used, they are added and logged with the same rules as Debug messages. > > This overrides the config setting `Debug.ShowTrace`. - > - `--mono-debug` @@ -53,7 +61,6 @@ Here's a quick list of what they are and what they do. > debugger server running on port 10000 on `localhost`. > > Implies `--debug`. - > - `--server` @@ -62,7 +69,6 @@ Here's a quick list of what they are and what they do. > When paired with `--mono-debug`, this option makes the Mono soft debugger act in server mode. It begins listening on > port 10000 on any address, and will pause startup (with no window) until a debugger is connected. I recommend using > SDB, but that is a command line debugger and a lot of people don't care for those. - > - `--no-yeet` @@ -73,7 +79,6 @@ Here's a quick list of what they are and what they do. > behaviour is disabled. > > Overrides the config setting `YeetMods`. - > - `--condense-logs` @@ -85,11 +90,11 @@ Here's a quick list of what they are and what they do. > Overrides the config setting `Debug.CondenseModLogs`. - `--plugin-logs` - + > Causes each plugins' log messages to be written to files in their own folder for ease of debugging. > > This was the default through 4.1.6, however is now disabled by default. > > Overrides the config setting `Debug.CreateModLogs`. - + ***