DQXIS-SDK
DQXIS-SDK copied to clipboard
Binding ESC
Would be nice to bring back ESC's action from original DQXI, as mentioned by Tim Allahn SnAckbarr in https://steamcommunity.com/app/1295510/discussions/0/3117025249764261520/:
The ESC key used to pull up the exit game prompt ("Are you sure you want to exit Dragon Quest XI?" yes/no). I believe this used to be a default behavior in older versions of UE4 Editor, which carried over into OG. Whether this was intentionally removed or a side-effect of the newer engine, i'm not sure.
This prompt now only appears when using ALT+F4 (which made the devs post a pretty stupid thread explaining how to exit with ALT+F4, "The announcement of how to close the game", lel)
I'm led to believe it calls this function:
// Function Engine.KismetSystemLibrary.QuitGame
// (Final, Native, Static, Public, BlueprintCallable)
// Parameters:
// class UObject* WorldContextObject (Parm, ZeroConstructor, IsPlainOldData)
// class APlayerController* SpecificPlayer (Parm, ZeroConstructor, IsPlainOldData)
// TEnumAsByte<EQuitPreference> QuitPreference (Parm, ZeroConstructor, IsPlainOldData)
void UKismetSystemLibrary::STATIC_QuitGame(class UObject* WorldContextObject, class APlayerController* SpecificPlayer, TEnumAsByte<EQuitPreference> QuitPreference)
{
static auto fn = UObject::FindObject<UFunction>("Function Engine.KismetSystemLibrary.QuitGame");
UKismetSystemLibrary_QuitGame_Params params;
params.WorldContextObject = WorldContextObject;
params.SpecificPlayer = SpecificPlayer;
params.QuitPreference = QuitPreference;
auto flags = fn->FunctionFlags;
fn->FunctionFlags |= 0x400;
UObject::ProcessEvent(fn, ¶ms);
fn->FunctionFlags = flags;
}
EQuitPreference has 2 possible values: (1) Quit, or (2) Background.
Not sure whether another function in KismetSystemLibrary is called before to print the "Are you sure you want to exit...?" MessageBox. My understanding is that this is usually set in Blueprints (perhaps i'll look there next)
https://docs.unrealengine.com/en-US/API/Runtime/Engine/Kismet/UKismetSystemLibrary/QuitGame/index.html (Runtime API) https://docs.unrealengine.com/en-US/BlueprintAPI/Game/QuitGame/index.html (Blueprint API)
Edit: I suppose even if not, we could always re-implement it ourselves, i.e.
std::wstringstream warning;
warning.clear();
warning << L"Any unsaved progress will be lost. Are you sure you want to exit the game?\r\n";
if (MessageBoxW(NULL, warning.str().c_str(), L"DRAGON QUEST XI S", MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
{
//writing example call in c#, not sure if method is overloaded based on UE4 documentation
//Engine.KismetSystemLibrary.QuitGame(0);
}
Seems the KismetSystemLibrary::QuitGame func uses the 'quit' / 'quit background' console commands internally, as can be seen in UnrealEngine-4.18\Engine\Source\Runtime\Engine\Private\KismetSystemLibrary.cpp
, doesn't look like either of those commands bring up the message box :(
I couldn't actually find the code in the EXE that brings the messagebox up, I'll have to take another look though since it must be in there somewhere... if there's a single function responsible for showing it we can probably just make an action that calls that function directly, otherwise I guess we'd have to reimplement it like you mentioned.
Got it to work, although oddly it only works when in game (not from main menu ;p )
ActionMappings=(ActionName="QuitGame", Key=Escape)
Submitting momentarily. (It's a little rough around the edges, so feel free to refactor / move things around / etc)
Submitted: https://github.com/emoose/DQXIS-SDK/pull/12/commits/cb170f99f65e57669e2ac582145423754ac44f91, https://github.com/emoose/DQXIS-SDK/pull/12
Good stuff, merged it and cleaned up a few things.
I think it might be worth keeping this issue open for now though - would still be nice if we could use the games code to show the messagebox, since I think that should be localized for all the langs the game supports too... still haven't had any luck finding where in the code it comes from though, maybe blueprints are involved or something...
although oddly it only works when in game (not from main menu ;p )
Yeah atm we only have hooks in place to add actions to AJackFieldPlayerController, so actions will only work on the field, not on menu/battle/etc. Maybe can look into hooking the code that adds the UI actions like UIButtonTop/UIButtonLeft/etc, I'd guess that those should be active all the time, will look into it later.
E: found a workaround for below by using UGameEngine::Exec("quit") directly, a little messy since we also had to patch FOutputDevice::Log so that it won't matter if we use nullptr as FOutputDevice param, but with all this the QuitGame action should work fine on UI menus now too.
There are still some small moments during battles where the QuitGame action isn't active, maybe need to find an InitActionMappings for battle class or something. If anyone notices any other times QuitGame doesn't work please let me know!
Found a way to let QuitGame action work in UIs (main menu / pause menu / battle menu...), just need to hook 0x14091B100 and make it call BindAction for QuitGame again.
There's two issues with it though, first is that the hooked function is ran by a bunch of UI classes, adding QuitGame to all of them makes them all call our QuitGame handler when bound key is pressed resulting in 4+ dialog boxes, that's probably not too hard to workaround though with a timer check or something though.
The other problem is that STATIC_QuitGame requires an APlayerController class to work properly, there is code in there which should handle nullptr being passed as the APlayerController (looks like it searches GObjects for an APlayerController to use) - but on the main menu I guess there currently isn't any APlayerController instances for it to use, so the QuitGame call doesn't do anything.
The QuitGame code seems to requite an APlayerController instance for it to run the "quit" command, maybe we can find a way to run it ourselves and skip the middleman, or maybe we could use StaticConstructObject_Internal to create our own instance of it, hmm...
Also, I just realized that right-clicking game taskbar icon and choosing Close will also make game show the ALT+F4 dialog, so I guess it must be showing that dialog as a result of the window itself being closed (or requesting to be closed at least)
Maybe there's a way to send WM_CLOSE to the games window for it to show that dialog for us, haven't had much luck with it yet though. (E: ah got it working, game needed WM_SYSCOMMAND/SC_CLOSE)
Also, I just realized that right-clicking game taskbar icon and choosing Close will also make game show the ALT+F4 dialog, so I guess it must be showing that dialog as a result of the window itself being closed (or requesting to be closed at least)
Maybe there's a way to send WM_CLOSE to the games window for it to show that dialog for us, haven't had much luck with it yet though. (E: ah got it working, game needed WM_SYSCOMMAND/SC_CLOSE)
Great! I had briefly looked at trying to do something with a standard AJackPlayerController instance after realizing AJackFieldPlayerController was only instantiated when in-game (and in 3D mode, at that), but didn't have much luck in the short time that I did.
Looks like that takes care of the major limitations
Oops closed this too early, forgot that there's still some moments where the QuitGame action isn't active, once those are sorted somehow we can close this.
Sorry, I never checked this in 2D mode. Looks like none of the binds work in 2D mode, for that matter: I did some digging and was curious if maybe a separate hook for AJackTriplePlayerController__InitActionMappings could make that possible?
i.e. (dllmain.cpp)
typedef void (*AJackTriplePlayerController__InitActionMappings_Fn)(AJackTriplePlayerController* thisptr);
AJackTriplePlayerController__InitActionMappings_Fn AJackTriplePlayerController__InitActionMappings_Orig;
void AJackTriplePlayerController__InitActionMappings_Hook(AJackTriplePlayerController* thisptr)
{
AJackTriplePlayerController__InitActionMappings_Orig(thisptr);
if (Options.CustomActions)
Init_CustomActions_Triple(thisptr);
}
// Also hook AJackTriplePlayerController::InitActionMappings
MH_CreateHook((LPVOID)(mBaseAddress + GameAddrs->AJackTriplePlayerController__InitActionMappings), AJackTriplePlayerController__InitActionMappings_Hook, (LPVOID*)&AJackTriplePlayerController__InitActionMappings_Orig);
(CustomActions.cpp)
//edit: we could probably just use an overloaded QuitGame method here, now that I think about it
void QuitGame_Triple(AJackTriplePlayerController* playerController)
{
// Workaround for multiple UI classes calling QuitGame immediately after each other
// Only show dialog if it's been 5 seconds or more since the last one:
time_t curTime;
time(&curTime);
if (curTime - LastDialog < 5)
return;
LastDialog = curTime;
// Make DQXIS show "Are you sure you want to quit" messagebox prompt
HWND mainWindow = FindMainWindow(GetCurrentProcessId());
if (mainWindow)
PostMessageA(mainWindow, WM_SYSCOMMAND, SC_CLOSE, 0);
}
void Init_CustomActions(AJackTriplePlayerController* playerController)
{
auto input = playerController->InputComponent;
input->BindAction("QuitGame", EInputEvent::IE_Pressed, playerController, QuitGame_Triple);
}
Ofc the (massive) piece i'm lacking is not knowing the offset to add into GameAddresses.cpp (finding it might be beyond my skillset atm, lol):
.AJackTriplePlayerController__InitActionMappings = 0xFFFFFF,
This is my best guess, at any rate. ^^"
0x9156C0
/ 0x8A8D80 (UWP)
might work for the address of AJackTriplePlayerController__InitActionMappings
, I don't know if it's actually part of AJackTriplePlayerController or not, but that func does call UInputComponent::BindAction for some TripleMoveUp & TripleMoveRight actions.
(E: I think the signature for that func might be void AJackTripleManager__InitActionMappings(AJackTripleManager* thisptr, UInputComponent* input);
)
I'll give it a try later on, might think about trying to hook all the funcs that use BindAction too... problem with that is figuring out what class each function belongs to though, normally would probably debug and set a breakpoint in that func, then get vftable addr from rcx/a1 and find out what UE4 class it belongs to, Denuvo fucks that up by moving vftables somewhere IDA doesn't seem to analyze properly though (and some reason loves to crash if I try navigating there manually >.>), maybe I'll have to see if this Game Pass trial card I got works for PC game pass too...
(of course since we changed QuitGame to use PostMessage we don't really need to care about the class it belongs to, can just be lazy and handle as void*... I'd prefer knowing about what it's hooking though, could always come in handy in future I guess)
E2: Not long after posting this my internet went down for ~3 hours, so I started on a list of all the funcs that use BindAction/BindAxis/BindAction2, luckily UWP build has vftables IDA could analyze, and most of these are virtual so wasn't too hard to find the classes for them. Quite a few here, don't think they all need hooking though, some call into the base classes bind function so we can just hook that to let us handle a bunch at once.
Is there any chance you could test out the binds against the things these classes relate to? Maybe there's only one or two here that actually need hooking.
Current list is here: https://gist.github.com/emoose/61be532af247f486902103096da5d38f Indented funcs call into the non-indented function above it, so those probably wouldn't need hooking. (E: updated a few of them with the actual class name found from debugging)
E3: AJackTripleManager hook seems to work too :D
Wow, nice!! Will dig through that list and look into trying out a few things shortly.