diff --git a/Spawner.vcxproj b/Spawner.vcxproj index 5df113d1..4633f0e2 100644 --- a/Spawner.vcxproj +++ b/Spawner.vcxproj @@ -17,6 +17,7 @@ + diff --git a/src/Misc/NoSaveLoadMode.cpp b/src/Misc/NoSaveLoadMode.cpp new file mode 100644 index 00000000..7946649c --- /dev/null +++ b/src/Misc/NoSaveLoadMode.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +const wchar_t* Fetch_CSF_Text(const char* label, const wchar_t* defaultText) +{ + std::wstring_view msg = StringTable::LoadString(label); + if (msg.empty() || msg.starts_with(L"MISSING")) + return defaultText; + return msg.data(); +} + +DEFINE_HOOK(0x686089, DoLose_RetryDialogForCampaigns, 0x7) +{ + if (!Spawner::GetConfig()->DisableSaveLoad) return 0; + enum { OK = 0x6860F6, Cancel = 0x6860EE}; + if (WWMessageBox::Instance.Process( + Fetch_CSF_Text("TXT_HARDCORE_FAILURE", L"GG"), + StringTable::LoadString("GUI:Restart"), + StringTable::LoadString("GUI:Leave"), nullptr) == WWMessageBox::Result::OK) + return OK; + + return Cancel; +} + +// disable load, save and delete buttons on the ingame menu +DEFINE_HOOK(0x4F17F6, sub_4F1720_DisableSaves, 0x6) +{ + if (!Spawner::GetConfig()->DisableSaveLoad) return 0; + GET(HWND, hDlg, EBP); + + HWND hLoad = GetDlgItem(hDlg, 1310); + SendMessageA(hLoad, 1202, 0, (LPARAM)Fetch_CSF_Text("GUI:CantLoad", L"Cannot Load")); + EnableWindow(hLoad, FALSE); + HWND hSave = GetDlgItem(hDlg, 1311); + SendMessageA(hSave, 1202, 0, (LPARAM)Fetch_CSF_Text("GUI:CantSave", L"Cannot Save")); + EnableWindow(hSave, FALSE); + HWND hDel = GetDlgItem(hDlg, 1312); + SendMessageA(hDel, 1202, 0, (LPARAM)L"----"); + EnableWindow(hDel, FALSE); + + return 0x4F1834; +} + +std::wstring HardCoreText {}; +DEFINE_HOOK(0x553076, LoadProgressMgr_Draw_ExtraText, 0x5) +{ + GET(LoadProgressManager*, self, EBP); + if (!Spawner::GetConfig()->DisableSaveLoad) return 0; + + Point2D pos + { + self->TitleBarRect.X + self->TitleBarRect.Width - 100, + self->TitleBarRect.Y + 10 + }; + if (HardCoreText.empty()) + HardCoreText = Fetch_CSF_Text("TXT_HARDCORE_MODE", L"HardCore"); + LEA_STACK(RectangleStruct*, pBnd, STACK_OFFSET(0x1268, -0x1204)); + if (auto logo = FileSystem::LoadSHPFile("hardcorelogo.shp")) + { + self->ProgressSurface->DrawSHP(FileSystem::PALETTE_PAL, logo, 0, &pos, pBnd, BlitterFlags::bf_400, 0, 0, ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); + } + else + { + self->ProgressSurface->DrawText(HardCoreText.c_str(), &pos, COLOR_RED); + } + + return 0; +} + +DEFINE_HOOK(0x68A107, ScenarioCass_ReadMapINI_eog, 0x6) +{ + if (!Spawner::GetConfig()->DisableSaveLoad) + return 0; + + GET(ScenarioClass*, sce, ESI); + sce->EndOfGame = true; + return 0x68A125; +} + +DEFINE_HOOK(0x6D0E20, TabClass_Draw_ccb, 0x6) +{ + if (HardCoreText.empty()) return 0; + auto rect = DSurface::Temp->GetRect(); + auto wanted = Drawing::GetTextDimensions(HardCoreText.c_str(), { 0,0 }, 0); + Point2D pt { + rect.Width - wanted.Width - 40, + rect.Height - 24 + }; + DSurface::Temp->DrawText(HardCoreText.c_str(), &pt, COLOR_WHITE); + return 0; +} + +DEFINE_HOOK(0x6C9357, MissionEndDlg_HardCoreExitText, 0x5) +{ + if (!Spawner::GetConfig()->DisableSaveLoad) return 0; + GET(HWND, hdlg, EBX); + + auto btnCtn = GetDlgItem(hdlg, 1059); + SendMessageA(btnCtn, 1202, 0, (LPARAM)StringTable::LoadString("GUI:ExitGame")); + + return 0; +} diff --git a/src/Spawner/Spawner.Config.cpp b/src/Spawner/Spawner.Config.cpp index 205eca9d..62d61f44 100644 --- a/src/Spawner/Spawner.Config.cpp +++ b/src/Spawner/Spawner.Config.cpp @@ -118,6 +118,7 @@ void SpawnerConfig::LoadFromINIFile(CCINIClass* pINI) ContinueWithoutHumans = pINI->ReadBool(pSettingsSection, "ContinueWithoutHumans", ContinueWithoutHumans); DefeatedBecomesObserver = pINI->ReadBool(pSettingsSection, "DefeatedBecomesObserver", DefeatedBecomesObserver); Observer_ShowAIOnSidebar = pINI->ReadBool(pSettingsSection, "Observer.ShowAIOnSidebar", Observer_ShowAIOnSidebar); + DisableSaveLoad = pINI->ReadBool(pSettingsSection, "DisableSaveLoad", false) && IsCampaign && !LoadSaveGame; #ifdef IS_CNCNET_YR_VER DisableChat = pINI->ReadBool(pSettingsSection, "DisableChat", DisableChat); #endif diff --git a/src/Spawner/Spawner.Config.h b/src/Spawner/Spawner.Config.h index fca7b2be..35191153 100644 --- a/src/Spawner/Spawner.Config.h +++ b/src/Spawner/Spawner.Config.h @@ -147,6 +147,7 @@ class SpawnerConfig bool ContinueWithoutHumans; bool DefeatedBecomesObserver; bool Observer_ShowAIOnSidebar; + bool DisableSaveLoad; #ifdef IS_CNCNET_YR_VER bool DisableChat; #endif @@ -245,6 +246,7 @@ class SpawnerConfig , ContinueWithoutHumans { false } , DefeatedBecomesObserver { false } , Observer_ShowAIOnSidebar { false } + , DisableSaveLoad { false } #ifdef IS_CNCNET_YR_VER , DisableChat { false } #endif diff --git a/src/Spawner/Spawner.Hook.cpp b/src/Spawner/Spawner.Hook.cpp index 0659dcad..0b2ddc29 100644 --- a/src/Spawner/Spawner.Hook.cpp +++ b/src/Spawner/Spawner.Hook.cpp @@ -73,6 +73,13 @@ DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6) // Skip load *.PKT, *.YRO and *.YRM map files Patch::Apply_LJMP(0x699AD9, 0x69A1B2); // SessionClass::Read_Scenario_Descriptions + + if (Spawner::GetConfig()->DisableSaveLoad) + { + Patch::Apply_LJMP(0x55DBCD, 0x55DC99); // Disable MainLoop_SaveGame (Spawner&Phobos) + Patch::Apply_RAW(0x67CEF0, { 0x33,0xC0,0xC3 }); // Corrupt savegame function + Patch::Apply_TYPED(0x83D560, { (DWORD)std::rand() }); // Corrupt save game magicn + } } return 0;