Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/Misc/SavedGamesInSubdir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,118 @@ DEFINE_HOOK(0x55DC85, MainLoop_SaveGame_SanitizeFilename, 0x7)

return 0x55DC90;
}

#pragma region nosaveload
#include <WWMessageBox.h>
#include <LoadProgressManager.h>
#include <array>

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;

WWMessageBox::Instance.Process(
Fetch_CSF_Text("TXT_HARDCORE_FAILURE", L"GG"),
StringTable::LoadString("TXT_OK"), nullptr, nullptr);

return 0x6860EE;
}

// disable load, save and delete buttons on the ingame menu

void PatchDialog181()
{
auto h = Game::hInstance;
auto r = FindResourceA(h, MAKEINTRESOURCE(181), RT_DIALOG);
if (!r) return;

auto sz = SizeofResource(h, r);
auto p = (BYTE*)LockResource(LoadResource(h, r));
if (!p) return;

DWORD old;
VirtualProtect(p, sz, PAGE_READWRITE, &old);
using namespace std::literals;
for (DWORD i = 0; i < sz; i += 2)
for (auto [f, t] : { std::pair{L"GUI:SaveGame"sv, L"GUI:CantSave"sv},
{L"GUI:LoadGame"sv, L"GUI:CantLoad"sv} })
{
if (!wcsncmp((wchar_t*)(p + i), f.data(), f.size()))
{
memcpy(p + i, t.data(), t.size() * 2);
memset(p + i + t.size() * 2, 0, (f.size() - t.size()) * 2);
}
}
VirtualProtect(p, sz, old, &old);
}

DEFINE_HOOK(0x4F17F6, sub_4F1720_DisableSaves, 0x6)
{
if (!Spawner::GetConfig()->DisableSaveLoad) return 0;
GET(HWND, hDlg, EBP);

enum { LoadGameButton = 1310, DeleteGameButton = 1312 };

for (int item = LoadGameButton; item <= DeleteGameButton; ++item)
{
if (HWND hItem = GetDlgItem(hDlg, item))
EnableWindow(hItem, 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(0x683C14, ScenarioClass_StartScenario_IntroMovie, 0x8)
{
GET_STACK(DWORD const, caller, 0xFC);
if (caller == 0x686501 || caller == 0x68629B)
return 0x683CF7;
return 0;
}

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;
}
#pragma endregion
1 change: 1 addition & 0 deletions src/Spawner/Spawner.Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/Spawner/Spawner.Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class SpawnerConfig
bool ContinueWithoutHumans;
bool DefeatedBecomesObserver;
bool Observer_ShowAIOnSidebar;
bool DisableSaveLoad;
#ifdef IS_CNCNET_YR_VER
bool DisableChat;
#endif
Expand Down Expand Up @@ -245,6 +246,7 @@ class SpawnerConfig
, ContinueWithoutHumans { false }
, DefeatedBecomesObserver { false }
, Observer_ShowAIOnSidebar { false }
, DisableSaveLoad { false }
#ifdef IS_CNCNET_YR_VER
, DisableChat { false }
#endif
Expand Down
10 changes: 10 additions & 0 deletions src/Spawner/Spawner.Hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
#include <Unsorted.h>
#include <CCINIClass.h>

void PatchDialog181();

DEFINE_HOOK(0x6BD7C5, WinMain_SpawnerInit, 0x6)
{
if (Spawner::Enabled)
Expand Down Expand Up @@ -73,6 +75,14 @@ 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
PatchDialog181();
}
}

return 0;
Expand Down
2 changes: 1 addition & 1 deletion src/Spawner/Spawner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ bool Spawner::StartScenario(const char* pScenarioName)

bool result = ScenarioClass::StartScenario(pScenarioName, 1, 0);

if (Spawner::Config->CustomMissionID != 0) // after parsing
if (Spawner::Config->CustomMissionID != 0 || Spawner::Config->DisableSaveLoad) // after parsing
ScenarioClass::Instance->EndOfGame = true;

return result;
Expand Down
Loading