diff --git a/Spawner.vcxproj b/Spawner.vcxproj index 709d454c..087d3003 100644 --- a/Spawner.vcxproj +++ b/Spawner.vcxproj @@ -41,6 +41,7 @@ + diff --git a/src/Misc/NoSaveLoadMode.cpp b/src/Misc/NoSaveLoadMode.cpp new file mode 100644 index 00000000..e51aaf48 --- /dev/null +++ b/src/Misc/NoSaveLoadMode.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include + +static bool IsDisableSaveLoadEnabled() +{ + return Spawner::Enabled && Spawner::GetConfig()->DisableSaveLoad; +} + +DEFINE_HOOK(0x6BD7CB, WinMain_InitNoSaveLoadMode, 0x5) +{ + if (IsDisableSaveLoadEnabled()) + { + Patch::Apply_LJMP(0x55DBCD, 0x55DC99); // Skip the initial save, including the Phobos autosave hook. + Patch::Apply_TYPED(0x83D560, { (DWORD)std::rand() }); // Invalidate the save game magic. + } + + return 0; +} + +DEFINE_HOOK(0x67CEF0, ScenarioClass_SaveGame_NoSaveLoadMode, 0x6) +{ + if (IsDisableSaveLoadEnabled()) + Debug::FatalErrorAndExit(Debug::ExitCode::SLFail, "Save attempted with DisableSaveLoad enabled. Terminating the game.\n"); + + return 0; +} + +DEFINE_HOOK(0x686089, DoLose_NoSaveLoadModeRetryDialog, 0x7) +{ + if (!IsDisableSaveLoadEnabled()) + return 0; + + enum { Restart = 0x6860F6, Leave = 0x6860EE }; + + const auto result = WWMessageBox::Instance.Process( + StringTable::TryFetchString("TXT_HARDCORE_FAILURE", L"GG"), + StringTable::LoadString("GUI:Restart"), + StringTable::LoadString("GUI:Leave"), nullptr); + + if (result == WWMessageBox::Result::OK) + return Restart; + + return Leave; +} + +// disable load, save and delete buttons on the ingame menu +DEFINE_HOOK(0x4F17F6, GameOptionsDialog_DisableSaveLoadButtons, 0x6) +{ + if (!IsDisableSaveLoadEnabled()) + return 0; + + GET(HWND, hDialog, EBP); + + HWND hLoadButton = GetDlgItem(hDialog, 1310); + SendMessageA(hLoadButton, 1202, 0, (LPARAM)StringTable::TryFetchString("GUI:CantLoad", L"Cannot Load")); + EnableWindow(hLoadButton, FALSE); + + HWND hSaveButton = GetDlgItem(hDialog, 1311); + SendMessageA(hSaveButton, 1202, 0, (LPARAM)StringTable::TryFetchString("GUI:CantSave", L"Cannot Save")); + EnableWindow(hSaveButton, FALSE); + + HWND hDeleteButton = GetDlgItem(hDialog, 1312); + SendMessageA(hDeleteButton, 1202, 0, (LPARAM)L"----"); + EnableWindow(hDeleteButton, FALSE); + + return 0x4F1834; +} + +std::wstring NoSaveLoadModeText { }; +DEFINE_HOOK(0x553076, LoadProgressManager_Draw_NoSaveLoadModeIndicator, 0x5) +{ + if (!IsDisableSaveLoadEnabled()) + return 0; + + GET(LoadProgressManager*, pLoadProgressManager, EBP); + if (NoSaveLoadModeText.empty()) + NoSaveLoadModeText = StringTable::TryFetchString("TXT_HARDCORE_MODE", L"HardCore"); + + Point2D position + { + pLoadProgressManager->TitleBarRect.X + pLoadProgressManager->TitleBarRect.Width - 100, + pLoadProgressManager->TitleBarRect.Y + 10 + }; + + LEA_STACK(RectangleStruct*, pBounds, STACK_OFFSET(0x1268, -0x1204)); + if (auto pLogoSHP = FileSystem::LoadSHPFile("hardcorelogo.shp")) + pLoadProgressManager->ProgressSurface->DrawSHP(FileSystem::PALETTE_PAL, pLogoSHP, 0, &position, pBounds, BlitterFlags::bf_400, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); + else + pLoadProgressManager->ProgressSurface->DrawText(NoSaveLoadModeText.c_str(), &position, COLOR_RED); + + return 0; +} + +DEFINE_HOOK(0x6D0E20, TabClass_Draw_NoSaveLoadModeText, 0x6) +{ + if (NoSaveLoadModeText.empty()) + return 0; + + auto surfaceRect = DSurface::Temp->GetRect(); + auto textDimensions = Drawing::GetTextDimensions(NoSaveLoadModeText.c_str(), { 0,0 }, 0); + Point2D position { + surfaceRect.Width - textDimensions.Width - 40, + surfaceRect.Height - 24 + }; + DSurface::Temp->DrawText(NoSaveLoadModeText.c_str(), &position, COLOR_WHITE); + + return 0; +} + +DEFINE_HOOK(0x68A1BD, ScenarioClass_ReadMapINI_SetEndOfGame, 0x6) +{ + if (!Spawner::Enabled || (!Spawner::GetConfig()->DisableSaveLoad && Spawner::GetConfig()->CustomMissionID == 0)) + return 0; + + GET(ScenarioClass*, pScenario, ESI); + pScenario->EndOfGame = true; + pScenario->SkipScore = false; + + return 0x68A1DB; +} + +DEFINE_HOOK(0x6C9357, MissionEndDialog_SetExitGameText, 0x5) +{ + if (ScenarioClass::Instance->EndOfGame) + { + GET(HWND, hDialog, EBX); + auto hContinueButton = GetDlgItem(hDialog, 1059); + SendMessageA(hContinueButton, 1202, 0, (LPARAM)StringTable::LoadString("GUI:ExitGame")); + } + + return 0; +} diff --git a/src/Spawner/Spawner.Config.cpp b/src/Spawner/Spawner.Config.cpp index 5908c490..407f18bb 100644 --- a/src/Spawner/Spawner.Config.cpp +++ b/src/Spawner/Spawner.Config.cpp @@ -119,6 +119,7 @@ void SpawnerConfig::LoadFromINIFile(CCINIClass* pINI) DefeatedBecomesObserver = pINI->ReadBool(pSettingsSection, "DefeatedBecomesObserver", DefeatedBecomesObserver); Observer_ShowAIOnSidebar = pINI->ReadBool(pSettingsSection, "Observer.ShowAIOnSidebar", Observer_ShowAIOnSidebar); DisableChat = pINI->ReadBool(pSettingsSection, "DisableChat", DisableChat); + DisableSaveLoad = pINI->ReadBool(pSettingsSection, "DisableSaveLoad", false) && IsCampaign && !LoadSaveGame; } } diff --git a/src/Spawner/Spawner.Config.h b/src/Spawner/Spawner.Config.h index 1432587e..0665c59d 100644 --- a/src/Spawner/Spawner.Config.h +++ b/src/Spawner/Spawner.Config.h @@ -148,6 +148,7 @@ class SpawnerConfig bool DefeatedBecomesObserver; bool Observer_ShowAIOnSidebar; bool DisableChat; + bool DisableSaveLoad; SpawnerConfig() // default values // Game Mode Options @@ -244,6 +245,7 @@ class SpawnerConfig , DefeatedBecomesObserver { false } , Observer_ShowAIOnSidebar { false } , DisableChat { false } + , DisableSaveLoad { false } { } void LoadFromINIFile(CCINIClass* pINI); diff --git a/src/Spawner/Spawner.Hook.cpp b/src/Spawner/Spawner.Hook.cpp index 0659dcad..8fee1b70 100644 --- a/src/Spawner/Spawner.Hook.cpp +++ b/src/Spawner/Spawner.Hook.cpp @@ -73,6 +73,7 @@ DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6) // Skip load *.PKT, *.YRO and *.YRM map files Patch::Apply_LJMP(0x699AD9, 0x69A1B2); // SessionClass::Read_Scenario_Descriptions + } return 0; diff --git a/src/Spawner/Spawner.cpp b/src/Spawner/Spawner.cpp index f7c76d50..2d8305ee 100644 --- a/src/Spawner/Spawner.cpp +++ b/src/Spawner/Spawner.cpp @@ -608,7 +608,8 @@ void Spawner::After_Main_Loop() && SessionClass::Instance.GameMode == GameMode::LAN && pConfig->AutoSaveInterval > 0; - const bool isAutoSaving = (doSaveSP || doSaveMP) + const bool isAutoSaving = !pConfig->DisableSaveLoad + && (doSaveSP || doSaveMP) && Unsorted::CurrentFrame == Spawner::NextAutoSaveFrame; // Schedule to make a save if it's time to autosave.