Skip to content

Commit 79f858b

Browse files
committed
Add map migrations system for automatic CS2 update breakage fixup
1 parent f562b4f commit 79f858b

10 files changed

Lines changed: 219 additions & 13 deletions

AMBuilder

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ for sdk_target in MMSPlugin.sdk_targets:
7272
'src/buttonwatch.cpp',
7373
'src/topdefender.cpp',
7474
'src/idlemanager.cpp',
75+
'src/mapmigrations.cpp',
7576
'sdk/entity2/entitysystem.cpp',
7677
'sdk/entity2/entityidentity.cpp',
7778
'sdk/entity2/entitykeyvalues.cpp',

CS2Fixes.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
<ClCompile Include="src\zombiereborn.cpp" />
214214
<ClCompile Include="src\entitylistener.cpp" />
215215
<ClCompile Include="src\leader.cpp" />
216+
<ClCompile Include="src\mapmigrations.cpp" />
216217
<ClCompile Include="sdk\tier1\convar.cpp" />
217218
<ClCompile Include="src\utils\entity.cpp" />
218219
<ClCompile Include="src\utils\plat_unix.cpp" />
@@ -291,6 +292,7 @@
291292
<ClInclude Include="src\zombiereborn.h" />
292293
<ClInclude Include="src\entitylistener.h" />
293294
<ClInclude Include="src\leader.h" />
295+
<ClCompile Include="src\mapmigrations.h" />
294296
<ClInclude Include="src\utils\entity.h" />
295297
<ClInclude Include="src\utils\module.h" />
296298
<ClInclude Include="src\utils\plat.h" />

CS2Fixes.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@
200200
<ClCompile Include="src\topdefender.cpp">
201201
<Filter>Source Files</Filter>
202202
</ClCompile>
203+
<ClCompile Include="src\mapmigrations.cpp">
204+
<Filter>Source Files</Filter>
205+
</ClCompile>
203206
</ItemGroup>
204207
<ItemGroup>
205208
<ClInclude Include="src\mempatch.h">
@@ -427,5 +430,8 @@
427430
<ClInclude Include="src\topdefender.h">
428431
<Filter>Header Files</Filter>
429432
</ClInclude>
433+
<ClInclude Include="src\mapmigrations.h">
434+
<Filter>Header Files</Filter>
435+
</ClInclude>
430436
</ItemGroup>
431437
</Project>

cfg/cs2fixes/cs2fixes.cfg

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,7 @@ entwatch_glow_team 0 // Whether dropped item glow is only visible to the tea
162162
// Hud settings
163163
cs2f_fix_hud_flashing 0 // Whether to fix hud flashing using a workaround, this BREAKS warmup so pick one or the other
164164
cs2f_disable_hud_outside_round 0 // Whether to disable hud messages that would flash when a round is not ongoing, since flashing fix cannot run then
165-
cs2f_hud_duration_leeway 2 // Extra seconds duration to leave hud messages visible (without priority), reduces transition flashes between different priority messages
165+
cs2f_hud_duration_leeway 2 // Extra seconds duration to leave hud messages visible (without priority), reduces transition flashes between different priority messages
166+
167+
// Map migrations
168+
cs2f_mapmigrations_20260121 2 // Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]

src/cs2fixes.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "interface.h"
4646
#include "leader.h"
4747
#include "map_votes.h"
48+
#include "mapmigrations.h"
4849
#include "networkstringtabledefs.h"
4950
#include "panoramavote.h"
5051
#include "patches.h"
@@ -356,6 +357,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
356357
g_pIdleSystem = new CIdleSystem();
357358
g_pPanoramaVoteHandler = new CPanoramaVoteHandler();
358359
g_pEWHandler = new CEWHandler();
360+
g_pMapMigrations = new CMapMigrations();
359361

360362
RegisterWeaponCommands();
361363

@@ -506,6 +508,9 @@ bool CS2Fixes::Unload(char* error, size_t maxlen)
506508
delete g_pEWHandler;
507509
}
508510

511+
if (g_pMapMigrations)
512+
delete g_pMapMigrations;
513+
509514
return true;
510515
}
511516

@@ -1036,6 +1041,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount
10361041
void CS2Fixes::Hook_ApplyGameSettings(KeyValues* pKV)
10371042
{
10381043
g_pMapVoteSystem->ApplyGameSettings(pKV);
1044+
g_pMapMigrations->ApplyGameSettings(pKV);
10391045
}
10401046

10411047
void CS2Fixes::Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList)

src/entitylistener.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "entity/cgamerules.h"
2626
#include "entwatch.h"
2727
#include "gameconfig.h"
28+
#include "mapmigrations.h"
2829
#include "plat.h"
2930

3031
CEntityListener* g_pEntityListener = nullptr;
@@ -55,6 +56,8 @@ void CEntityListener::OnEntitySpawned(CEntityInstance* pEntity)
5556

5657
if (g_cvarEnableEntWatch.Get())
5758
EW_OnEntitySpawned(pEntity);
59+
60+
g_pMapMigrations->OnEntitySpawned(pEntity);
5861
}
5962

6063
void CEntityListener::OnEntityCreated(CEntityInstance* pEntity)

src/map_votes.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ void CMapVoteSystem::HandlePlayerMapLookup(CCSPlayerController* pController, std
661661
// Check if input is numeric (workshop ID)
662662
if (iWorkshopId != 0)
663663
{
664-
CWorkshopDetailsQuery::Create(iWorkshopId, pController, callbackSuccess);
664+
CMapSystemWorkshopDetailsQuery::Create(iWorkshopId, pController, callbackSuccess);
665665
return;
666666
}
667667

@@ -1386,7 +1386,7 @@ std::pair<int, std::shared_ptr<CMap>> CMapVoteSystem::GetMapInfoByIdentifiers(co
13861386
return {-1, nullptr};
13871387
}
13881388

1389-
std::shared_ptr<CWorkshopDetailsQuery> CWorkshopDetailsQuery::Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess)
1389+
std::shared_ptr<CMapSystemWorkshopDetailsQuery> CMapSystemWorkshopDetailsQuery::Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess)
13901390
{
13911391
uint64 iWorkshopIDArray[1] = {iWorkshopId};
13921392
UGCQueryHandle_t hQuery = g_steamAPI.SteamUGC()->CreateQueryUGCDetailsRequest(iWorkshopIDArray, 1);
@@ -1400,14 +1400,14 @@ std::shared_ptr<CWorkshopDetailsQuery> CWorkshopDetailsQuery::Create(uint64 iWor
14001400
g_steamAPI.SteamUGC()->SetAllowCachedResponse(hQuery, 0);
14011401
SteamAPICall_t hCall = g_steamAPI.SteamUGC()->SendQueryUGCRequest(hQuery);
14021402

1403-
auto pQuery = std::make_shared<CWorkshopDetailsQuery>(hQuery, iWorkshopId, pController, callbackSuccess);
1403+
auto pQuery = std::make_shared<CMapSystemWorkshopDetailsQuery>(hQuery, iWorkshopId, pController, callbackSuccess);
14041404
g_pMapVoteSystem->AddWorkshopDetailsQuery(pQuery);
1405-
pQuery->m_CallResult.Set(hCall, pQuery.get(), &CWorkshopDetailsQuery::OnQueryCompleted);
1405+
pQuery->m_CallResult.Set(hCall, pQuery.get(), &CMapSystemWorkshopDetailsQuery::OnQueryCompleted);
14061406

14071407
return pQuery;
14081408
}
14091409

1410-
void CWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed)
1410+
void CMapSystemWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed)
14111411
{
14121412
CCSPlayerController* pController = m_hController.Get();
14131413
SteamUGCDetails_t details;

src/map_votes.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ class CGroup
132132
};
133133

134134
// Implementation is a bit hardcoded for HandlePlayerMapLookup use
135-
class CWorkshopDetailsQuery : public std::enable_shared_from_this<CWorkshopDetailsQuery>
135+
class CMapSystemWorkshopDetailsQuery : public std::enable_shared_from_this<CMapSystemWorkshopDetailsQuery>
136136
{
137137
public:
138-
CWorkshopDetailsQuery(UGCQueryHandle_t hQuery, uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess) :
138+
CMapSystemWorkshopDetailsQuery(UGCQueryHandle_t hQuery, uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess) :
139139
m_hQuery(hQuery), m_iWorkshopId(iWorkshopId), m_callbackSuccess(callbackSuccess)
140140
{
141141
if (pController)
@@ -149,13 +149,13 @@ class CWorkshopDetailsQuery : public std::enable_shared_from_this<CWorkshopDetai
149149
}
150150
}
151151

152-
static std::shared_ptr<CWorkshopDetailsQuery> Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess);
152+
static std::shared_ptr<CMapSystemWorkshopDetailsQuery> Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess);
153153

154154
private:
155155
void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed);
156156

157157
UGCQueryHandle_t m_hQuery;
158-
CCallResult<CWorkshopDetailsQuery, SteamUGCQueryCompleted_t> m_CallResult;
158+
CCallResult<CMapSystemWorkshopDetailsQuery, SteamUGCQueryCompleted_t> m_CallResult;
159159
uint64 m_iWorkshopId;
160160
bool m_bConsole;
161161
CHandle<CCSPlayerController> m_hController;
@@ -218,8 +218,8 @@ class CMapVoteSystem
218218
void ProcessGroupCooldowns();
219219
bool ReloadMapList(bool bReloadMap = true);
220220
bool ConvertCooldownsKVToJSON();
221-
void AddWorkshopDetailsQuery(std::shared_ptr<CWorkshopDetailsQuery> pQuery) { m_vecWorkshopDetailsQueries.push_back(pQuery); }
222-
void RemoveWorkshopDetailsQuery(std::shared_ptr<CWorkshopDetailsQuery> pQuery) { m_vecWorkshopDetailsQueries.erase(std::remove(m_vecWorkshopDetailsQueries.begin(), m_vecWorkshopDetailsQueries.end(), pQuery), m_vecWorkshopDetailsQueries.end()); }
221+
void AddWorkshopDetailsQuery(std::shared_ptr<CMapSystemWorkshopDetailsQuery> pQuery) { m_vecWorkshopDetailsQueries.push_back(pQuery); }
222+
void RemoveWorkshopDetailsQuery(std::shared_ptr<CMapSystemWorkshopDetailsQuery> pQuery) { m_vecWorkshopDetailsQueries.erase(std::remove(m_vecWorkshopDetailsQueries.begin(), m_vecWorkshopDetailsQueries.end(), pQuery), m_vecWorkshopDetailsQueries.end()); }
223223
void SetPlayerNomination(int iPlayerSlot, int iMapIndex) { m_arrPlayerNominations[iPlayerSlot] = iMapIndex; }
224224

225225
private:
@@ -247,7 +247,7 @@ class CMapVoteSystem
247247
std::filesystem::file_time_type m_timeMapListModified = std::filesystem::file_time_type::min();
248248
std::weak_ptr<CTimer> m_pDownloadProgressTimer;
249249
std::weak_ptr<CTimer> m_pRateLimitedDownloadTimer;
250-
std::vector<std::shared_ptr<CWorkshopDetailsQuery>> m_vecWorkshopDetailsQueries;
250+
std::vector<std::shared_ptr<CMapSystemWorkshopDetailsQuery>> m_vecWorkshopDetailsQueries;
251251
};
252252

253253
extern CMapVoteSystem* g_pMapVoteSystem;

src/mapmigrations.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* =============================================================================
3+
* CS2Fixes
4+
* Copyright (C) 2023-2025 Source2ZE
5+
* =============================================================================
6+
*
7+
* This program is free software; you can redistribute it and/or modify it under
8+
* the terms of the GNU General Public License, version 3.0, as published by the
9+
* Free Software Foundation.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14+
* details.
15+
*
16+
* You should have received a copy of the GNU General Public License along with
17+
* this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
#include "mapmigrations.h"
21+
#include "cs2fixes.h"
22+
#include "entity.h"
23+
#include "entity/cbasemodelentity.h"
24+
#include "utils.h"
25+
26+
CMapMigrations* g_pMapMigrations = nullptr;
27+
28+
const time_t g_time20260121 = 1769036239;
29+
30+
CConVar<int> g_cvarMapMigrations20260121("cs2f_mapmigrations_20260121", FCVAR_NONE, "Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]", 2);
31+
32+
void CMapMigrations::ApplyGameSettings(KeyValues* pKV)
33+
{
34+
m_timeMapUpdated = std::numeric_limits<time_t>::max();
35+
36+
// Don't run on default maps
37+
if (!pKV->FindKey("launchoptions") || !pKV->FindKey("launchoptions")->FindKey("customgamemode"))
38+
return;
39+
40+
CMapMigrationWorkshopDetailsQuery::Create(pKV->FindKey("launchoptions")->GetUint64("customgamemode"));
41+
}
42+
43+
void CMapMigrations::OnEntitySpawned(CEntityInstance* pEntity)
44+
{
45+
if (g_cvarMapMigrations20260121.Get() == 1 || (g_cvarMapMigrations20260121.Get() == 2 && m_timeMapUpdated < g_time20260121))
46+
Migration_20260121(pEntity);
47+
}
48+
49+
void CMapMigrations::Migration_20260121(CEntityInstance* pEntity)
50+
{
51+
CBaseEntity* pBaseEntity = (CBaseEntity*)pEntity;
52+
53+
if (!V_strcasecmp(pEntity->GetClassname(), "func_door_rotating"))
54+
{
55+
uint32 spawnFlags = pBaseEntity->m_spawnflags();
56+
57+
// Force add One-way flag if not present
58+
if (!(spawnFlags & 16))
59+
pBaseEntity->m_spawnflags = spawnFlags + 16;
60+
}
61+
62+
CBaseModelEntity* pModelEntity = pBaseEntity->AsBaseModelEntity();
63+
64+
if (pModelEntity)
65+
{
66+
RenderMode_t renderMode = pModelEntity->m_nRenderMode();
67+
68+
// Legacy kRenderTransAlpha
69+
if (renderMode == 4)
70+
pModelEntity->m_nRenderMode = kRenderTransAlpha;
71+
// Legacy kRenderNone
72+
else if (renderMode == 10)
73+
pModelEntity->m_nRenderMode = kRenderNone;
74+
// All other removed render modes, fall back to normal
75+
else if (renderMode > kRenderNormal)
76+
pModelEntity->m_nRenderMode = kRenderNormal;
77+
}
78+
}
79+
80+
void CMapMigrations::UpdateMapUpdateTime(time_t timeMapUpdated)
81+
{
82+
m_timeMapUpdated = timeMapUpdated;
83+
84+
CBaseEntity* pTarget = nullptr;
85+
86+
// May be called late, so also check any existing entities first
87+
while ((pTarget = UTIL_FindEntityByName(pTarget, "*")))
88+
OnEntitySpawned(pTarget);
89+
}
90+
91+
std::shared_ptr<CMapMigrationWorkshopDetailsQuery> CMapMigrationWorkshopDetailsQuery::Create(uint64 iWorkshopId)
92+
{
93+
uint64 iWorkshopIDArray[1] = {iWorkshopId};
94+
UGCQueryHandle_t hQuery = g_steamAPI.SteamUGC()->CreateQueryUGCDetailsRequest(iWorkshopIDArray, 1);
95+
96+
if (hQuery == k_UGCQueryHandleInvalid)
97+
{
98+
Panic("Map migrations failed to find current map update time: failed to query workshop map information for ID %llu\n", iWorkshopId);
99+
return nullptr;
100+
}
101+
102+
g_steamAPI.SteamUGC()->SetAllowCachedResponse(hQuery, 0);
103+
SteamAPICall_t hCall = g_steamAPI.SteamUGC()->SendQueryUGCRequest(hQuery);
104+
105+
auto pQuery = std::make_shared<CMapMigrationWorkshopDetailsQuery>(hQuery, iWorkshopId);
106+
g_pMapMigrations->AddWorkshopDetailsQuery(pQuery);
107+
pQuery->m_CallResult.Set(hCall, pQuery.get(), &CMapMigrationWorkshopDetailsQuery::OnQueryCompleted);
108+
109+
return pQuery;
110+
}
111+
112+
void CMapMigrationWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed)
113+
{
114+
SteamUGCDetails_t details;
115+
116+
if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !g_steamAPI.SteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK)
117+
Panic("Map migrations failed to find current map update time: failed to query workshop map information for ID %llu\n", m_iWorkshopId);
118+
else
119+
g_pMapMigrations->UpdateMapUpdateTime(details.m_rtimeUpdated);
120+
121+
g_steamAPI.SteamUGC()->ReleaseQueryUGCRequest(m_hQuery);
122+
g_pMapMigrations->RemoveWorkshopDetailsQuery(shared_from_this());
123+
}

src/mapmigrations.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* =============================================================================
3+
* CS2Fixes
4+
* Copyright (C) 2023-2025 Source2ZE
5+
* =============================================================================
6+
*
7+
* This program is free software; you can redistribute it and/or modify it under
8+
* the terms of the GNU General Public License, version 3.0, as published by the
9+
* Free Software Foundation.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14+
* details.
15+
*
16+
* You should have received a copy of the GNU General Public License along with
17+
* this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
#pragma once
21+
22+
#include "KeyValues.h"
23+
#include "convar.h"
24+
#include "entitysystem.h"
25+
#include "steam/isteamugc.h"
26+
#include <vector>
27+
28+
extern CConVar<int> g_cvarMapMigrations20260121;
29+
30+
class CMapMigrationWorkshopDetailsQuery : public std::enable_shared_from_this<CMapMigrationWorkshopDetailsQuery>
31+
{
32+
public:
33+
CMapMigrationWorkshopDetailsQuery(UGCQueryHandle_t hQuery, uint64 iWorkshopId) :
34+
m_hQuery(hQuery), m_iWorkshopId(iWorkshopId)
35+
{}
36+
37+
static std::shared_ptr<CMapMigrationWorkshopDetailsQuery> Create(uint64 iWorkshopId);
38+
39+
private:
40+
void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed);
41+
42+
UGCQueryHandle_t m_hQuery;
43+
CCallResult<CMapMigrationWorkshopDetailsQuery, SteamUGCQueryCompleted_t> m_CallResult;
44+
uint64 m_iWorkshopId;
45+
};
46+
47+
class CMapMigrations
48+
{
49+
public:
50+
void ApplyGameSettings(KeyValues* pKV);
51+
void OnEntitySpawned(CEntityInstance* pEntity);
52+
void Migration_20260121(CEntityInstance* pEntity);
53+
void UpdateMapUpdateTime(time_t timeMapUpdated);
54+
void AddWorkshopDetailsQuery(std::shared_ptr<CMapMigrationWorkshopDetailsQuery> pQuery) { m_vecWorkshopDetailsQueries.push_back(pQuery); }
55+
void RemoveWorkshopDetailsQuery(std::shared_ptr<CMapMigrationWorkshopDetailsQuery> pQuery) { m_vecWorkshopDetailsQueries.erase(std::remove(m_vecWorkshopDetailsQueries.begin(), m_vecWorkshopDetailsQueries.end(), pQuery), m_vecWorkshopDetailsQueries.end()); }
56+
57+
private:
58+
time_t m_timeMapUpdated = std::numeric_limits<time_t>::max();
59+
std::vector<std::shared_ptr<CMapMigrationWorkshopDetailsQuery>> m_vecWorkshopDetailsQueries;
60+
};
61+
62+
extern CMapMigrations* g_pMapMigrations;

0 commit comments

Comments
 (0)