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.