diff --git a/Spawner.vcxproj b/Spawner.vcxproj index ebb20a7c..a3280b9d 100644 --- a/Spawner.vcxproj +++ b/Spawner.vcxproj @@ -67,9 +67,7 @@ - - - + @@ -96,7 +94,6 @@ - diff --git a/cspell.json b/cspell.json index 57fd1320..b537d52b 100644 --- a/cspell.json +++ b/cspell.json @@ -354,6 +354,7 @@ "NOINTERFACE", "NOMINMAX", "NOWINDOWCHANGES", + "NOZORDER", "ntdll", "ntfnce", "NTRLMD", diff --git a/src/Spawner/Spawner.Config.cpp b/src/Spawner/Spawner.Config.cpp index 1b4faedc..33004f97 100644 --- a/src/Spawner/Spawner.Config.cpp +++ b/src/Spawner/Spawner.Config.cpp @@ -54,13 +54,14 @@ void SpawnerConfig::LoadFromINIFile(CCINIClass* pINI) } { // SaveGame Options - LoadSaveGame = pINI->ReadBool(pSettingsSection, "LoadSaveGame", LoadSaveGame); - /* SavedGameDir */ pINI->ReadString(pSettingsSection, "SavedGameDir", SavedGameDir, SavedGameDir, sizeof(SavedGameDir)); - /* SaveGameName */ pINI->ReadString(pSettingsSection, "SaveGameName", SaveGameName, SaveGameName, sizeof(SaveGameName)); - CustomMissionID = pINI->ReadInteger(pSettingsSection, "CustomMissionID", 0); - AutoSaveCount = pINI->ReadInteger(pSettingsSection, "AutoSaveCount", AutoSaveCount); - AutoSaveInterval = pINI->ReadInteger(pSettingsSection, "AutoSaveInterval", AutoSaveInterval); - NextAutoSaveNumber = pINI->ReadInteger(pSettingsSection, "NextAutoSaveNumber", NextAutoSaveNumber); + LoadSaveGame = pINI->ReadBool(pSettingsSection, "LoadSaveGame", LoadSaveGame); + /* SavedGameDir */ pINI->ReadString(pSettingsSection, "SavedGameDir", SavedGameDir, SavedGameDir, sizeof(SavedGameDir)); + /* SaveGameName */ pINI->ReadString(pSettingsSection, "SaveGameName", SaveGameName, SaveGameName, sizeof(SaveGameName)); + CustomMissionID = pINI->ReadInteger(pSettingsSection, "CustomMissionID", 0); + AutoSaveCount = pINI->ReadInteger(pSettingsSection, "AutoSaveCount", AutoSaveCount); + AutoSaveInterval = pINI->ReadInteger(pSettingsSection, "AutoSaveInterval", AutoSaveInterval); + NextAutoSaveNumber = pINI->ReadInteger(pSettingsSection, "NextAutoSaveNumber", NextAutoSaveNumber); + ShowMPSaveGameButton = pINI->ReadBool(pSettingsSection, "ShowMPSaveGameButton", ShowMPSaveGameButton); } { // Scenario Options diff --git a/src/Spawner/Spawner.Config.h b/src/Spawner/Spawner.Config.h index 1432587e..f7834af8 100644 --- a/src/Spawner/Spawner.Config.h +++ b/src/Spawner/Spawner.Config.h @@ -103,6 +103,7 @@ class SpawnerConfig int AutoSaveCount; int AutoSaveInterval; int NextAutoSaveNumber; + bool ShowMPSaveGameButton; // Scenario Options int Seed; @@ -179,6 +180,7 @@ class SpawnerConfig , AutoSaveCount { 5 } , AutoSaveInterval { 7200 } , NextAutoSaveNumber { 0 } + , ShowMPSaveGameButton { true } // Scenario Options , Seed { 0 } diff --git a/src/UI/Dialogs.cpp b/src/UI/Dialogs.cpp deleted file mode 100644 index 856019be..00000000 --- a/src/UI/Dialogs.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - So here's a first example on how to modify the game's dialogs! :3 - - To port a dialog from YR to the DLL, add a new .rc file and don't bother editing it - using MSVC's inbuilt tools. - Open gamemd.exe in ResHacker, open the selected dialog and copy the whole resource script to - the new .rc file. - - Don't edit the dialog ID. That would be adding a new dialog and that's a pain to get working - apparently (I don't know how to do that yet). - Add #include to the first line of the script. - If STYLE doesn't list WS_CAPTION, remove the CAPTION "" line. - - Adding new controls is best done in ResHacker, as MSVC wants to add too much stupid - stuff to things (resource.h and crap like that). - Also remember to set the control properties like the game originals to avoid problems. - Finally, be aware that MSVC will automatically add WS_VISIBLE to the controls. - I don't know why, but I know it sucks, as you'll see when you use the RMG with this version. - I've yet to find a way to suppress this behavior. - - If you add buttons, they'll look like shit until you register them in the dialog function. - - Adding new dialogs requires many funny hacks I'll try to figure later. - - To make our systems consitent: - New control IDs within a dialog start with 5000. - - Each dialog code should get its own cpp file to avoid major confusion. - This central source file contains an addition to YR's "FetchResource" - that's able to find resources within this DLL as well. - It's important for it to be just an addition so other DLLs can do this as well! - - Happy GUI modding! - -pd -*/ - -#include "Dialogs.h" - -#include -#include -#include -#include -#include - -//4A3B4B, 9 - NOTE: This overrides a call, but it's absolute, so don't worry. -DEFINE_HOOK(0x4A3B4B, FetchResource, 0x9) -{ - if (!Spawner::Enabled) - return 0; - - HMODULE hModule = static_cast(Main::hInstance); //hModule and hInstance are technically the same... - GET(LPCTSTR, lpName, ECX); - GET(LPCTSTR, lpType, EDX); - - if(HRSRC hResInfo = FindResource(hModule, lpName, lpType)) { - if(HGLOBAL hResData = LoadResource(hModule, hResInfo)) { - LockResource(hResData); - R->EAX(hResData); - - return 0x4A3B73; //Resource locked and loaded (omg what a pun), return! - } - } - return 0; //Nothing was found, try the game's own resources. -} - -/* -DEFINE_HOOK(0x60411B, Game_DialogFunc_Subtext_Load, 0x5) -{ - GET(int, DlgItemID, EAX); - - Dialogs::StatusString = nullptr; - if(DlgItemID == -1) { - return 0x604120; - } - if(DlgItemID >= ARES_GUI_START) { - switch(DlgItemID) { - case ARES_CHK_RMG_URBAN_AREAS: - Dialogs::StatusString = "STT:RMGUrbanAreas"; - break; - case ARES_CHK_MULTIENGINEER: - Dialogs::StatusString = "STT:MultiEngineer"; - break; - default: - Dialogs::StatusString = "GUI:Debug"; - } - return 0x604135; - } - return 0x604126; -} - - -DEFINE_HOOK(0x604136, Game_DialogFunc_Subtext_Propagate, 0x5) -{ - if(Dialogs::StatusString) { - R->EAX(Dialogs::StatusString); - return 0x60413B; - } - return 0; -} -*/ diff --git a/src/UI/Dialogs.h b/src/UI/Dialogs.h deleted file mode 100644 index 4f02a04d..00000000 --- a/src/UI/Dialogs.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -/* -enum VanillaDialogs : uint16_t -{ - SingleplayerGameOptionsDialog = 181, - MultiplayerGameOptionsDialog = 3002 -}; -*/ -// For now this can only contain copies of the original dialogs with same IDs -enum SpawnerCustomDialogs : uint16_t -{ - MultiplayerGameOptionsDialog = 3002, // added a save button - - First = 3002, - Last = 3002 -}; - -class Dialogs { -public: - -}; diff --git a/src/UI/Hooks.SaveButton.cpp b/src/UI/Hooks.SaveButton.cpp new file mode 100644 index 00000000..ca72cabf --- /dev/null +++ b/src/UI/Hooks.SaveButton.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +enum GameOptionsControlID +{ + SaveGameButton = 1311, + GameControlsButton = 1313, + AbortMissionButton = 1314 +}; + +// Create the button before StandardWndProc registers and formats the controls. +DEFINE_HOOK(0x4F11D6, MultiplayerGameOptionsDialog_AddSaveButton, 0x6) +{ + enum { MultiplayerGameOptionsDialog = 3002 }; + + if (!Spawner::Enabled || !Spawner::GetConfig()->ShowMPSaveGameButton) + return 0; + + GET(UINT, message, EBX); + GET(HWND, hDialog, ESI); + GET(const WORD*, initData, EBP); + + if (message != WM_INITDIALOG || !initData || *initData != MultiplayerGameOptionsDialog) + return 0; + + const auto hExistingSave = Imports::GetDlgItem(hDialog, SaveGameButton); + if (hExistingSave) + return 0; + + const auto hControls = Imports::GetDlgItem(hDialog, GameControlsButton); + const auto hAbort = Imports::GetDlgItem(hDialog, AbortMissionButton); + if (!hControls || !hAbort) + return 0; + + RECT controlsRect; + RECT abortRect; + const auto hasControlsRect = Imports::GetWindowRect(hControls, &controlsRect); + const auto hasAbortRect = Imports::GetWindowRect(hAbort, &abortRect); + if (!hasControlsRect || !hasAbortRect) + return 0; + + const int buttonStepY = abortRect.top - controlsRect.top; + MapWindowPoints(nullptr, hDialog, reinterpret_cast(&abortRect), 2); + const int width = abortRect.right - abortRect.left; + const int height = abortRect.bottom - abortRect.top; + const auto hInstance = reinterpret_cast(Imports::GetWindowLongA(hDialog, GWL_HINSTANCE)); + const auto hSave = Imports::CreateWindowExA(0, "BUTTON", "GUI:SaveGame", + WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, + abortRect.left, abortRect.top, width, height, hDialog, + reinterpret_cast(SaveGameButton), hInstance, nullptr); + + if (!hSave) + return 0; + + const auto font = Imports::SendMessageA(hAbort, WM_GETFONT, 0, 0); + Imports::SendMessageA(hSave, WM_SETFONT, font, FALSE); + + // Place the save button after game controls in the child window order. + Imports::SetWindowPos(hSave, hControls, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + Imports::SetWindowPos(hAbort, nullptr, abortRect.left, abortRect.top + buttonStepY, + 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; +} + +DEFINE_HOOK(0x609299, UI_IsStaticAndOrOwnerDraw_MultiplayerGameOptionsDialog, 0x5) +{ + if (!Spawner::Enabled) + return 0; + + enum { RetTrue = 0x609693 }; + GET(int, dlgCtrlID, EAX); + + if (dlgCtrlID == SaveGameButton) + return RetTrue; + + return 0; +} diff --git a/src/UI/Hooks.cpp b/src/UI/Hooks.cpp deleted file mode 100644 index c020fdee..00000000 --- a/src/UI/Hooks.cpp +++ /dev/null @@ -1,17 +0,0 @@ - -#include "Dialogs.h" -#include - -#include -#include - -DEFINE_HOOK(0x609299, UI_IsStaticAndOrOwnerDraw_MultiplayerGameOptionsDialog, 0x5) -{ - if (!Spawner::Enabled) - return 0; - - enum { RetFalse = 0x609664, RetTrue = 0x609693 }; - - GET(int, dlgCtrlID, EAX); - return (dlgCtrlID == 1314 || dlgCtrlID == 1313 || dlgCtrlID == 1311) ? RetTrue : RetFalse; -} diff --git a/src/UI/MultiplayerGameOptionsDialog.rc b/src/UI/MultiplayerGameOptionsDialog.rc deleted file mode 100644 index 01783b17..00000000 --- a/src/UI/MultiplayerGameOptionsDialog.rc +++ /dev/null @@ -1,14 +0,0 @@ -#include - -3002 DIALOG 0, 0, 533, 369 -STYLE DS_SETFONT | WS_CHILD -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -FONT 8, "MS Sans Serif" -{ - CONTROL "GUI:GameOptions", 1684, STATIC, SS_CENTER | WS_CHILD | WS_VISIBLE | WS_GROUP, 425, 1, 108, 10 - CONTROL "GUI:GameControls", 1313, BUTTON, BS_OWNERDRAW | WS_CHILD | WS_VISIBLE, 425, 122, 108, 23 - CONTROL "GUI:SaveGame", 1311, BUTTON, BS_OWNERDRAW | WS_CHILD | WS_VISIBLE, 425, 149, 108, 23 - CONTROL "GUI:AbortMission", 1314, BUTTON, BS_OWNERDRAW | WS_CHILD | WS_VISIBLE, 425, 176, 108, 23 - CONTROL "GUI:ResumeMission", 1670, BUTTON, BS_OWNERDRAW | WS_CHILD | WS_VISIBLE, 425, 346, 108, 23 - CONTROL "GUI:Blank", 1685, STATIC, SS_LEFT | SS_CENTERIMAGE | WS_CHILD | WS_VISIBLE, 2, 355, 303, 12 -} \ No newline at end of file