Skip to content

Commit

Permalink
Implement CustomMissionID for 3rd party mission filtering and extra i…
Browse files Browse the repository at this point in the history
…nfo on S/L
  • Loading branch information
chaserli committed Mar 10, 2024
1 parent 0bb085a commit a4a6511
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 1 deletion.
200 changes: 200 additions & 0 deletions src/Misc/SavedGamesInSubdir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
#include <Utilities/Macro.h>
#include <Spawner/Spawner.h>

#include <HouseClass.h>
#include <LoadOptionsClass.h>

#include <filesystem>
#include <optional>

namespace SavedGames
{
Expand Down Expand Up @@ -161,3 +165,199 @@ DEFINE_HOOK(0x67FD26, LoadOptionsClass_ReadSaveInfo_SGInSubdir, 0x5)

return 0;
}


//issue #18 : Save game filter for 3rd party campaigns
namespace SavedGames
{
struct CustomMissionID
{
static constexpr wchar_t* SaveName = L"CustomMissionID";

int Number;

CustomMissionID() : Number { Spawner::GetConfig()->CustomMissionID } { }

CustomMissionID(int num) : Number { num } { }

operator int() const { return Number; }
};

// More fun
int HowManyTimesISavedForThisScenario = 0;

struct ExtraMetaInfo
{
static constexpr wchar_t* SaveName = L"Spawner Extra Info";

int CurrentFrame;
int SavedCount;
int TechnoCount;

explicit ExtraMetaInfo()
:CurrentFrame { Unsorted::CurrentFrame }
, SavedCount { HowManyTimesISavedForThisScenario }
, TechnoCount { TechnoClass::Array->Count }
{
}
};

template<typename T>
bool AppendToStorage(IStorage* pStorage)
{
IStream* pStream = nullptr;
bool ret = false;
HRESULT hr = pStorage->CreateStream(
T::SaveName,
STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
0,
0,
&pStream
);

if (SUCCEEDED(hr) && pStream != nullptr)
{
T info {};
ULONG written = 0;
hr = pStream->Write(&info, sizeof(info), &written);
ret = SUCCEEDED(hr) && written == sizeof(info);
pStream->Release();
}

return ret;
}


template<typename T>
std::optional<T> ReadFromStorage(IStorage* pStorage)
{
IStream* pStream = nullptr;
bool hasValue = false;
HRESULT hr = pStorage->OpenStream(
T::SaveName,
NULL,
STGM_READ | STGM_SHARE_EXCLUSIVE,
0,
&pStream
);

T info;

if (SUCCEEDED(hr) && pStream != nullptr)
{
ULONG read = 0;
hr = pStream->Read(&info, sizeof(info), &read);
hasValue = SUCCEEDED(hr) && read == sizeof(info);

pStream->Release();
}

return hasValue ? std::make_optional(info) : std::nullopt;
}

}

DEFINE_HOOK(0x559921, LoadOptionsClass_FillList_FilterFiles, 0x6)
{
GET(FileEntryClass*, pEntry, EBP);
enum { NullThisEntry = 0x559959 };

// there was a qsort later and filters out these but we could have just removed them right here
if (pEntry->IsWrongVersion || !pEntry->IsValid)
{
GameDelete(pEntry);
return NullThisEntry;
};

static OLECHAR wNameBuffer[0x100] {};
SavedGames::FormatPath(Main::readBuffer, pEntry->Filename.data());
MultiByteToWideChar(CP_UTF8, 0, Main::readBuffer, -1, wNameBuffer, std::size(wNameBuffer));
IStorage* pStorage = nullptr;
bool shouldDelete = false;
if (SUCCEEDED(StgOpenStorage(wNameBuffer, NULL,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStorage)
))
{
auto id = SavedGames::ReadFromStorage<SavedGames::CustomMissionID>(pStorage);

if (Spawner::GetConfig()->CustomMissionID != id.value_or(0))
shouldDelete = true;
}

if (pStorage)
pStorage->Release();

if (shouldDelete)
{
GameDelete(pEntry);
return NullThisEntry;
}

return 0;
}

DEFINE_HOOK(0x683AFE, StartNewScenario_ClearCounter, 0x6)
{
SavedGames::HowManyTimesISavedForThisScenario = 0;
return 0;
}


// Write : A la fin
DEFINE_HOOK(0x67D2E3, SaveGame_AdditionalInfoForClient, 0x6)
{
GET_STACK(IStorage*, pStorage, STACK_OFFSET(0x4A0, -0x490));
using namespace SavedGames;
HowManyTimesISavedForThisScenario++;

if (pStorage)
{
if (SessionClass::IsCampaign() && Spawner::GetConfig()->CustomMissionID)
AppendToStorage<CustomMissionID>(pStorage);
if (AppendToStorage<ExtraMetaInfo>(pStorage))
Debug::Log("[Spawner] Extra meta info appended on sav file\n");
}

return 0;
}

// Read : Au debut
DEFINE_HOOK(0x67E4DC, LoadGame_AdditionalInfoForClient, 0x7)
{
LEA_STACK(const wchar_t*, filename, STACK_OFFSET(0x518, -0x4F4));
IStorage* pStorage = nullptr;
using namespace SavedGames;

if (SUCCEEDED(StgOpenStorage(filename, NULL,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStorage)
))
{
if (auto id = ReadFromStorage<CustomMissionID>(pStorage))
{
int num = id->Number;
Debug::Log("[Spawner] sav file CustomMissionID = %d\n", num);
Spawner::GetConfig()->CustomMissionID = num;
ScenarioClass::Instance->EndOfGame = true;
}
else
{
Spawner::GetConfig()->CustomMissionID = 0;
}

if (auto info = ReadFromStorage<ExtraMetaInfo>(pStorage))
{
Debug::Log("[Spawner] CurrentFrame = %d, TechnoCount = %d, HowManyTimesSaved = %d \n"
, info->CurrentFrame
, info->TechnoCount
, info->SavedCount
);
HowManyTimesISavedForThisScenario = info->SavedCount;
}
}
if (pStorage)
pStorage->Release();

return 0;
}
1 change: 1 addition & 0 deletions src/Spawner/Spawner.Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void SpawnerConfig::LoadFromINIFile(CCINIClass* pINI)
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);

{ // Scenario Options
Seed = pINI->ReadInteger(pSettingsSection, "Seed", Seed);
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 @@ -94,6 +94,7 @@ class SpawnerConfig
bool LoadSaveGame;
char SavedGameDir[MAX_PATH]; // Nested paths are also supported, e.g. "Saved Games\\Yuri's Revenge"
char SaveGameName[60];
int CustomMissionID;

// Scenario Options
int Seed;
Expand Down Expand Up @@ -161,6 +162,7 @@ class SpawnerConfig
, LoadSaveGame { false }
, SavedGameDir { "Saved Games" }
, SaveGameName { "" }
, CustomMissionID { 0 }

// Scenario Options
, Seed { 0 }
Expand Down
14 changes: 13 additions & 1 deletion src/Spawner/Spawner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "ProtocolZero.LatencyLevel.h"
#include <Utilities/Debug.h>
#include <Utilities/DumperTypes.h>
#include <Utilities/Patch.h>

#include <GameOptionsClass.h>
#include <GameStrings.h>
Expand Down Expand Up @@ -294,7 +295,18 @@ bool Spawner::StartNewScenario(const char* pScenarioName)
if (SessionClass::IsCampaign())
{
pGameModeOptions->Crates = true;
return ScenarioClass::StartScenario(pScenarioName, 1, 0);

// Rename MISSIONMD.INI to this
// because Ares has LoadScreenText.Color and Phobos has Starkku's PR #1145
if (Spawner::Config->CustomMissionID) // before parsing
Patch::Apply_RAW(0x839724, "Spawn.ini");

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

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

return result;
}
else if (SessionClass::IsSkirmish())
{
Expand Down

0 comments on commit a4a6511

Please sign in to comment.