@@ -187,11 +187,13 @@ bool g_bOBSDetected = false;
187187#include < tchar.h>
188188#include < psapi.h>
189189#include < tlhelp32.h>
190+ #include < direct.h>
190191#undef CreateEvent
191192#endif
192193
193194#ifdef LINUX
194195#include " neo_fixup_glshaders.h"
196+ #include < unistd.h>
195197#endif
196198
197199#endif
@@ -1303,6 +1305,90 @@ static void NeoClantag_ChangeCallback(IConVar *cvar, [[maybe_unused]] const char
13031305}
13041306#endif
13051307
1308+ #ifdef NEO
1309+ #define SZ_USERCUSTOM_DIR " download/user_custom"
1310+ // NEO NOTE (nullsystem): Delete custom downloaded sprays from other players on
1311+ // startup and shutdown.
1312+ // NEO JANK (nullsystem): It's done on both startup and shutdown mostly to
1313+ // mitigate SDK filesystem API bug so it likely remove all the expected files
1314+ // on shutdown.
1315+ static void NeoDeleteDownloadedSprays ()
1316+ {
1317+ // NEO NOTE (nullsystem): Only deal with the .dat hexdec named files directly,
1318+ // no other directories (IFilesystem can't delete directories) or files deleted.
1319+ FileFindHandle_t findHdlFL;
1320+ for (const char *pszFNameFLDir = filesystem->FindFirst (SZ_USERCUSTOM_DIR " /*" , &findHdlFL);
1321+ pszFNameFLDir && findHdlFL != FILESYSTEM_INVALID_FIND_HANDLE;
1322+ pszFNameFLDir = filesystem->FindNext (findHdlFL))
1323+ {
1324+ // Sanity check, expects directory of two character hexadecimal
1325+ const bool bIsValidFLDir = filesystem->FindIsDirectory (findHdlFL)
1326+ && (V_strlen (pszFNameFLDir) == 2 )
1327+ && V_isxdigit (pszFNameFLDir[0 ])
1328+ && V_isxdigit (pszFNameFLDir[1 ]);
1329+ if (!bIsValidFLDir)
1330+ {
1331+ continue ;
1332+ }
1333+
1334+ char szSLRelPath[MAX_PATH];
1335+ V_sprintf_safe (szSLRelPath, SZ_USERCUSTOM_DIR " /%s" , pszFNameFLDir);
1336+
1337+ char szSLSearch[MAX_PATH];
1338+ V_sprintf_safe (szSLSearch, " %s/*.dat" , szSLRelPath);
1339+
1340+ FileFindHandle_t findHdlSL;
1341+ for (const char *pszFNameSLDat = filesystem->FindFirst (szSLSearch, &findHdlSL);
1342+ pszFNameSLDat && findHdlSL != FILESYSTEM_INVALID_FIND_HANDLE;
1343+ pszFNameSLDat = filesystem->FindNext (findHdlSL))
1344+ {
1345+ // Filename sanity check: Make sure filename is of this format: 00000000.dat - ffffffff.dat
1346+ static constexpr int BASE_FNAMESIZE = 8 ;
1347+ const int iFNameSLDatSize = V_strlen (pszFNameSLDat);
1348+ const bool bIsExpectedFNameSize = (iFNameSLDatSize == (BASE_FNAMESIZE + sizeof (" .dat" ) - 1 ));
1349+ const bool bIsFile = !filesystem->FindIsDirectory (findHdlSL);
1350+ if (!bIsExpectedFNameSize || !bIsFile)
1351+ {
1352+ continue ;
1353+ }
1354+
1355+ bool bFNameIsHexDigit = true ;
1356+ for (int i = 0 ; bFNameIsHexDigit && i < BASE_FNAMESIZE; ++i)
1357+ {
1358+ bFNameIsHexDigit = V_isxdigit (pszFNameSLDat[i]);
1359+ }
1360+ if (!bFNameIsHexDigit)
1361+ {
1362+ continue ;
1363+ }
1364+
1365+ char szFullFPathSLDat[MAX_PATH];
1366+ V_sprintf_safe (szFullFPathSLDat, SZ_USERCUSTOM_DIR " /%s/%s" , pszFNameFLDir, pszFNameSLDat);
1367+
1368+ // NEO JANK (nullsystem): filesystem API seems buggy having earlier file(s) in alphanum order
1369+ // unable to recognize those files and remove them. When RemoveFile was called wasn't the issue
1370+ // and changing to calling it after Find... usages didn't solve it.
1371+ // So instead, doing this cleanup on both startup and shutdown could somewhat mitigate it.
1372+ filesystem->RemoveFile (szFullFPathSLDat);
1373+ }
1374+ filesystem->FindClose (findHdlSL);
1375+
1376+ // Remove this assumingly empty directory with rmdir
1377+ // It only removes empty directory so generally safe call
1378+ // and don't need to double check.
1379+ char szFullPath[MAX_PATH];
1380+ filesystem->RelativePathToFullPath_safe (szSLRelPath, " MOD" , szFullPath);
1381+ #if defined(WIN32)
1382+ // Both slash deliminator is valid in Windows, don't need to convert
1383+ _rmdir (szFullPath);
1384+ #elif defined(LINUX)
1385+ rmdir (szFullPath);
1386+ #endif
1387+ }
1388+ filesystem->FindClose (findHdlFL);
1389+ }
1390+ #endif
1391+
13061392// -----------------------------------------------------------------------------
13071393// Purpose: Called after client & server DLL are loaded and all systems initialized
13081394// -----------------------------------------------------------------------------
@@ -1409,6 +1495,8 @@ void CHLClient::PostInit()
14091495 {
14101496 V_memset (gStreamerModeNames [i], ' .' , 5 );
14111497 }
1498+
1499+ NeoDeleteDownloadedSprays ();
14121500#endif // NEO
14131501
14141502 if ( !r_lightmap_bicubic_set.GetBool () && materials )
@@ -1427,6 +1515,10 @@ void CHLClient::PostInit()
14271515// -----------------------------------------------------------------------------
14281516void CHLClient::Shutdown ( void )
14291517{
1518+ #ifdef NEO
1519+ NeoDeleteDownloadedSprays ();
1520+ #endif
1521+
14301522 if (g_pAchievementsAndStatsInterface)
14311523 {
14321524 g_pAchievementsAndStatsInterface->ReleasePanel ();
0 commit comments