goatcorp / FFXIVQuickLauncher

Custom launcher for FFXIV
https://goatcorp.github.io/
GNU General Public License v3.0
2.77k stars 327 forks source link

SpecialK compatibility, "Game exited prematurely" #457

Open Corrodias opened 3 years ago

Corrodias commented 3 years ago

Describe the bug here: When using the SpecialK graphics wrapper, which you can think of as being similar to ReShade, the launcher thinks the game has exited immediately and does not inject its plugins. No changes from the launcher occur in the game.

The reason I want to use SpecialK is, long story short, to get g-sync to be active when in borderless windowed mode. Otherwise, I have to use fullscreen, which causes delays when using alt+tab or when any other windows draw over the game.

Steps to reproduce the bug: 1) Download SpecialK; I tried a few versions, one of which is the current: 21.04.16: https://gitlab.special-k.info/Kaldaien/SpecialK/-/releases/SK_21_04_16 - you can find more info here. https://discourse.differentk.fyi/t/special-k-v-21-04-04-now-with-d3d12-support/1656 2) Install it simply by extracting the SpecialK64.dll file from the package into the game folder alongside the .exe and rename it to either "dxgi.dll" or "d3d11.dll"; both result in this failure. It will automatically create a default config file with the same name but extension ".ini" upon launching the game. 3) Attempt to launch the game with the launcher. 4) The game launches, but no plugins are injected, and the launcher displays an error dialog saying that the game has exited prematurely. 5) However, SpecialK is active.

Expected behavior I expect the game to launch and the plugins to be injected as normal, while SpecialK is in effect.

Other information (please complete the following information): Operating System: Windows 10 Pro, build 19042 (20H2) XIVLauncher version: 5.5.3.0 Launching as admin?: false Using Steam?: false Addons/Plugins enabled: true Context: XG LaunchGame Git Hash: 5.5.3-0-g5149e97 64bit?: True DX11?: True

I tried removing all plugins and the addon folder, and I tried it with my plugins present (MarketBoardPlugin, MiniCactpotSolver, AoLBar, RezPls, SonarPlugin); both situations exhibit the same problem.

Additional information The error:

System.Exception: Game exited prematurely
   at XIVLauncher.Game.Launcher.LaunchGame(String sessionId, Int32 region, Int32 expansionLevel, Boolean isSteamIntegrationEnabled, Boolean isSteamServiceAccount, String additionalArguments, DirectoryInfo gamePath, Boolean isDx11, ClientLanguage language, Boolean encryptArguments) in D:\a\FFXIVQuickLauncher\FFXIVQuickLauncher\XIVLauncher\Game\Launcher.cs:line 269

The log:

2021-06-19 05:57:10.765 -05:00 [INF] CurrentAccount: corrodias-False-False
2021-06-19 05:57:10.766 -05:00 [INF] StartLogin() called
2021-06-19 05:57:11.793 -05:00 [INF] XivGame::Login(steamServiceAccount:False, cache:False)
2021-06-19 05:57:11.793 -05:00 [INF] Cache is invalid or disabled, logging in normally.
2021-06-19 05:57:13.007 -05:00 [INF] OAuth login successful - playable:True terms:True region:2 expack:3
2021-06-19 05:57:14.182 -05:00 [INF] XivGame::LaunchGame(steamIntegration:False, steamServiceAccount:False, args:)
2021-06-19 05:57:14.182 -05:00 [INF] ArgumentBuilder::DeriveKey() rawTickCount:4257117796 ticks:4257117796 key:4257087488
2021-06-19 05:57:14.182 -05:00 [INF] ArgumentBuilder::BuildEncrypted() checksum:"V"

Upon a successful run, without SpecialK installed, this is followed by Starting RunnableAddon XIVLauncher in-game features, but that is not the case here.

Corrodias commented 3 years ago

Doing some debugging, it's clear that SpecialK is indeed causing a new process to be launched with a new PID. I don't know why SK is making a new process, though; I'm trying to figure out how to post a question on their forum to ask.

Corrodias commented 3 years ago

Rather, it doesn't happen normally. This seems to be a consequence of trying to change the DACLs on the process so soon after launching, perhaps while SpecialK is still setting up (whatever those are; I understand little about that). Maybe this is some kind of wacky race condition.

Adding a 1-second thread sleep after the process.WaitForInputIdle() call fixes this problem. I don't know whether you would consider this an acceptable solution, but I'm going to use a copy of the program with that sleep inserted for a while and report back on my findings.

reiichi001 commented 3 years ago

You can already set an injection delay for Dalamud in the launcher program settings.

Corrodias commented 3 years ago

I gave that a try just now; unfortunately, it doesn't help. The delay must happen after process.WaitForInputIdle but before PInvoke.SetSecurityInfo. I tried moving it immediately after the latter, and it didn't work there.

In other words, it's not plugin loading that needs to be delayed for this to work, but rather the DACL change. I wish I had any explanation at all for why that is the case, but so it is.

Corrodias commented 3 years ago

Kaldaien's response when I asked why this might be causing a new process to take the place of the original is this:

It’s probably triggering SK’s anti-debug protection code. Why is that launcher removing privileges while the process is suspended? SK’s designed to prevent tampering with debug privileges while threads are suspended. It will resume the thread and revert any attempts made to hide a thread from debuggers, detach debuggers, etc. There’s no legitimate reason to do that sort of thing. So if the launcher’s waiting on the thread that it’s tampering with, it’s going to have run to completion already. I’d try nixing the anti-debug stuff 🙂

In that vein, what is the purpose of DisableSeDebug?

goaaats commented 3 years ago

Square Enix' launcher sets the ACLs for the game process to read only, so no other process can get a read/write handle on it without admin rights. This breaks XL and a lot of other third party tools like the Discord/Steam overlay, ACT, etc.

If the game doesn't see the right ACLs set, it will restart itself with the right ACLs in WinMain. That is why XL needs to do it before the game has a chance to enter the main thread.

If there's any better way to go about this, feel free to let me know.

Corrodias commented 3 years ago

Okay, so you're getting the handle to the process before it has a chance to lock itself down; that makes sense. And you're changing the ACLs to allow XL (and other programs) to be able to modify the process without an admin token, such as injecting plugins, okay.

But why are you calling DisableSeDebug(lpProcessInformation.hProcess); what's that for? The game seems to run just fine even if I comment out that line (with SK removed).

Sounds like it's not necessarily SK that's restarting the game, but that we could be triggering its own ACL fix. My hypothesis is that you're (normally) changing the ACLs just after the game checks them, but with SK added to the pipeline, the game's initialization is delayed just long enough -- or WaitForInputIdle returns slightly earlier -- causing you to change the ACLs before the game checks them, leading to the game noticing they're wrong and restarting itself. That would explain why a brief sleep before changing the ACLs works. It would also imply that there's no harm in sleeping briefly in all cases, with or without SK.

Corrodias commented 3 years ago

Would you consider a pull request that adds the needed delay before changing the ACLs? Would you need it to be a configurable delay? I would still like to know the purpose of removing SeDebugPrivilege from the game process, also.

TyroneSama commented 3 years ago

I use Special K to wrangle XIV's horrible fullscreen mode, so compatibility would be cool if the changes wouldn't break other stuff. For the moment, I've had to just disable Dalamud plugins completely.

DAOWAce commented 3 years ago

Were any changes made to the plugin system since this issue was opened?

For almost 2 weeks now I've been using XIVLauncher, SpecialK and GShade together, complete with multiple Dalamud plugins, and with tools like Teamcraft, ACT and Concept Matrix all running in background.

Sometimes the game bugs out or crashes when changing resolution (a problem with SpecialK), but that's the only thing that's happened.

As an aside, I'm using a more updated version of SpecialK (21.07.22), obtained from Kaldaien's discord. The one on his website is many months out of date, this might be contributing to me not having the compatibility issues other people are seeing.

Corrodias commented 3 years ago

For the record, I was using SK 21.04.04, a the time. I am no longer playing and can't provide more recent information.

MiKE41 commented 2 years ago

I had problems with SpecialK too, similar to OP. Sometimes the game would launch fine, most of the time the launcher would say the game exited prematurely.

The problem appears to be that SpecialK interferes with process.WaitForInputIdle(); in nativeAclFix.cs because it creates threads and then sets their priority to idle before the game executable fully loads, which in turn causes the launcher to change the games ACL too early. The ACL check actually happens in FFXIVs code. The WinMain function runs some check (look at code surrounding the string "FFIXV_WAKEUP_CNT"), discovers the more permissive ACL and restarts the game with the restrictive ACL again, exiting the copy that the launcher made (thus giving the error "Game exited prematurely"), while to the user it looks like the game is actually running fine.

image

My (admittedly crappy) solution was to modify the launcher to put a delay right after process.WaitForInputIdle(); which lets SpecialK load and the game launch before modifying the process ACL. A delay of 3000ms has been working flawlessly for me for the past few months. https://github.com/MiKE41/FFXIVQuickLauncher/commit/539cc0ce8efb314f0062c258c15752d9b03a33ec

DAOWAce commented 2 years ago

An update from me on this too: Newer SK versions have ceased working properly with XIVLauncher for me.

If I update from 21.7.22, I can no longer open SpecialK's interface, and it seems to break features of the tool (like fps limiting). I can only get it working properly by deleting the config file, to which it works the next boot, and that boot only. Subsequent launches have it broken like before. Certain versions do work properly, but only without XIVLauncher loaded.

It also causes XIVLauncher to error most of the time (mainly that exited prematurely one), irrespective of the injection delay.

I also noticed that XIVLauncher (or Dalamud) has detection for ReShade/GShade. Unfortunately, if you use SpecialK, it thinks you're using Re/Gshade, or at least attempts to look for it and spits out an error (but still works):

2021-10-08 19:24:15.548 -04:00 [ERR] Could not find reshade DXGISwapChain::runtime_present offset!
System.Collections.Generic.KeyNotFoundException: Can't find a signature of F6 C2 01 0F 85 ?? ?? ?? ??
   at Dalamud.Game.SigScanner.Scan(IntPtr baseAddress, Int32 size, String signature) in C:\goatsoft\companysecrets\dalamud\Game\SigScanner.cs:line 133
   at Dalamud.Game.SigScanner.ScanText(String signature) in C:\goatsoft\companysecrets\dalamud\Game\SigScanner.cs:line 299
   at Dalamud.Game.Internal.DXGI.SwapChainVtableResolver.Setup64Bit(SigScanner sig) in C:\goatsoft\companysecrets\dalamud\Game\Internal\DXGI\SwapChainVtableResolver.cs:line 48

I load GShade via SpecialK, and am not a dev so I have no idea how to make it look for a different file in a different location,

I don't have the capability to compile @MiKE41's fork to test the change, but if it does indeed resolve the incompatibility with no downsides, I'd very much like to see it implemented. The OP of this issue did mention the stuff originally and said they were going to use a version with it changed, but only talked about submitting a PR, not mentioning whatever edits they made..

MiKE41 commented 2 years ago

I had an idea over the weekend for a more reliable method of waiting for the game process to load and implemented it here: https://github.com/MiKE41/FFXIVQuickLauncher/commit/ba03eca79d98e1d44b96d32e272e4ca722e91b52

Instead of waiting for a thread in the game to become idle, wait for the game to create a window and inject after that. Since the ACL check by the game is one of the first things WinMain does, we should always do the modification after that.

To test I started the game 10 times each with WaitForInputIdle and MainWindowHandle, with and without SpecialK to see the success rate and how long each method was waiting for the game to load before modifying the ACL.

Without SpecialK: WaitForInputIdle waited for an average of 725ms and the game launched w/addons injected properly 10/10 times. MainWindowHandle != 0 waited for an average of 721ms and the game launched w/addons injected properly 10/10 times.

With SpecialK: WaitForInputIdle waited for an average of 488ms and the game launched w/addons injected properly 6/10 times. (successful launches were approx 784ms, failed launches were 44ms) MainWindowHandle != 0 waited for an average of 820ms and the game launched w/addons injected properly 10/10 times.

DAOWAce commented 2 years ago

Coming back to this, I see there's a "New" and "Legacy" option for loading Dalamud.

Unfortunately, the new one, which seems to be designed for this issue's very reason, is failing to load 100% of the time with the same error.

Using the legacy option, Dalamud is also instantly killing my game client on launch. Once Dalamud it injects, game dies. It was working 'fine' the first time after trying the update.. until a plugin caused the game to lockup, but now after removing the plugins I just can't seem to get Dalamud working at all. (Or it can sometimes load, but very rarely)

Removing SpecialK lets the "New" method work, ironically.

I guess it's time to move to the global injector so Dalamud can actually work properly until some developer who loves SK attempts to get them playing nicely. :(

Granted, I am running older software versions (Windows included), so other people may have a different experience.