Skip to content

Commit c8d7dc4

Browse files
committed
refactor(wip): filesystem to avoid fragmentation
in many places, you had to have certain files in certain folders autoreset / api key / ihud images / tas folder in Portal 2 crosshairs in portal2 cfgs wouldnt autocomplete from p2common etc now you can put anything anywhere and it just works ™️ if a feature tries to read a file it can do it from any search path and if it writes it will first try to overwrite an existing e.g.
1 parent d7ea677 commit c8d7dc4

25 files changed

+204
-149
lines changed

src/Checksum.cpp

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "Utils.hpp"
44
#include "Event.hpp"
55
#include "Modules/Engine.hpp"
6+
#include "Modules/FileSystem.hpp"
67
#include "Utils/ed25519/ed25519.h"
78
#include "Version.hpp"
89

@@ -257,20 +258,33 @@ static void calcFileSums(std::map<std::string, uint32_t> *out, std::vector<std::
257258
static void initFileSums() {
258259
std::vector<std::string> paths;
259260
try {
260-
for (auto &ent : std::filesystem::recursive_directory_iterator(".")) {
261-
if (ent.status().type() == std::filesystem::file_type::regular || ent.status().type() == std::filesystem::file_type::symlink) {
262-
auto path = ent.path().string();
263-
std::replace(path.begin(), path.end(), '\\', '/');
264-
265-
bool dlc = path.find("portal2_dlc") != std::string::npos &&
266-
path.find("portal2_dlc1") == std::string::npos &&
267-
path.find("portal2_dlc2") == std::string::npos;
268-
269-
if (Utils::EndsWith(path, ".nut")
270-
|| (Utils::EndsWith(path, ".vpk") && dlc)
271-
|| path.find("scripts/talker") != std::string::npos)
272-
{
273-
paths.push_back(path);
261+
auto searchpaths = fileSystem->GetSearchPaths();
262+
for (auto searchpath : searchpaths) {
263+
auto iterator = std::filesystem::recursive_directory_iterator(searchpath, std::filesystem::directory_options::follow_directory_symlink);
264+
for (auto &ent : iterator) {
265+
if (ent.status().type() == std::filesystem::file_type::directory) {
266+
// skip directories that are other search paths
267+
for (auto otherpath : searchpaths) {
268+
if (std::filesystem::equivalent(ent.path(), otherpath)) {
269+
iterator.disable_recursion_pending();
270+
break;
271+
}
272+
}
273+
}
274+
if (ent.status().type() == std::filesystem::file_type::regular || ent.status().type() == std::filesystem::file_type::symlink) {
275+
auto path = ent.path().string();
276+
std::replace(path.begin(), path.end(), '\\', '/');
277+
278+
bool dlc = path.find("portal2_dlc") != std::string::npos &&
279+
path.find("portal2_dlc1") == std::string::npos &&
280+
path.find("portal2_dlc2") == std::string::npos;
281+
282+
if (Utils::EndsWith(path, ".nut")
283+
|| (Utils::EndsWith(path, ".vpk") && dlc)
284+
|| path.find("scripts/talker") != std::string::npos)
285+
{
286+
paths.push_back(path);
287+
}
274288
}
275289
}
276290
}

src/Command.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "Command.hpp"
22

33
#include "Game.hpp"
4+
#include "Modules/FileSystem.hpp"
45
#include "Modules/Tier1.hpp"
56
#include "SAR.hpp"
67

@@ -219,15 +220,32 @@ int _FileCompletionFunc(std::string extension, std::string rootdir, int exp_args
219220
try {
220221
std::set<std::string> sorted;
221222

222-
for (auto &file : std::filesystem::directory_iterator(rootdir + std::string("/") + dirpart)) {
223-
try {
224-
if (file.is_directory() || Utils::EndsWith(file.path().extension().string(), extension)) {
225-
std::string path = dirpart + file.path().stem().string();
226-
std::replace(path.begin(), path.end(), '\\', '/');
227-
if (file.is_directory()) path += "/";
228-
sorted.insert(path);
223+
auto gamedirs = fileSystem->GetSearchPaths();
224+
for (auto gamedir : gamedirs) {
225+
if (std::filesystem::is_directory(gamedir + rootdir + "/" + dirpart)) {
226+
for (auto &file : std::filesystem::directory_iterator(gamedir + rootdir + "/" + dirpart)) {
227+
try {
228+
if (file.is_directory() || Utils::EndsWith(file.path().extension().string(), extension)) {
229+
std::string path = dirpart + file.path().stem().string();
230+
std::replace(path.begin(), path.end(), '\\', '/');
231+
if (file.is_directory()) {
232+
path += "/";
233+
// This is a bit of a hack, but it works
234+
// avoids confusion such as "do_stuff portal2/..." == "do_stuff ..."
235+
bool skip = false;
236+
for (auto otherdir : gamedirs) {
237+
if (std::filesystem::equivalent(otherdir, gamedir + rootdir + "/" + path)) {
238+
skip = true;
239+
break;
240+
}
241+
}
242+
if (skip) continue;
243+
}
244+
sorted.insert(path);
245+
}
246+
} catch (std::system_error &e) {
247+
}
229248
}
230-
} catch (std::system_error &e) {
231249
}
232250
}
233251

src/Features/AutoSubmit.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Features/Hud/Toasts.hpp"
55
#include "Features/NetMessage.hpp"
66
#include "Modules/Engine.hpp"
7+
#include "Modules/FileSystem.hpp"
78
#include "Modules/Server.hpp"
89
#include "Utils/json11.hpp"
910
#include <cctype>
@@ -371,7 +372,8 @@ static void submitTime(int score, std::string demopath, bool coop, const char *m
371372
}
372373

373374
static void loadApiKey(bool output_nonexist) {
374-
if (!std::filesystem::exists(API_KEY_FILE)) {
375+
auto filepath = fileSystem->FindFileSomewhere(API_KEY_FILE);
376+
if (!filepath.has_value()) {
375377
if (output_nonexist) {
376378
console->Print("API key file " API_KEY_FILE " does not exist!\n");
377379
}
@@ -380,7 +382,7 @@ static void loadApiKey(bool output_nonexist) {
380382

381383
std::string key;
382384
{
383-
std::ifstream f(API_KEY_FILE);
385+
std::ifstream f(filepath.value());
384386
std::stringstream buf;
385387
buf << f.rdbuf();
386388
key = buf.str();

src/Features/Camera.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -742,12 +742,9 @@ CON_COMMAND(sar_cam_path_export,
742742
if (rate <= 0) rate = 60;
743743

744744
// check if file exists before writing
745-
std::ifstream testFile(filename.c_str());
746-
if (testFile.good()) {
747-
testFile.close();
745+
if (std::filesystem::exists(filename)) {
748746
return console->Print("File \"%s\" exists and cannot be overwritten.\n", filename.c_str());
749747
}
750-
testFile.close();
751748

752749
std::ofstream file(filename.c_str());
753750

src/Features/ClassDumper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "Modules/Client.hpp"
44
#include "Modules/Console.hpp"
55
#include "Modules/Engine.hpp"
6+
#include "Modules/FileSystem.hpp"
67
#include "Modules/Server.hpp"
78
#include "SAR.hpp"
89
#include "Utils/SDK.hpp"
@@ -24,7 +25,8 @@ ClassDumper::ClassDumper()
2425
void ClassDumper::Dump(bool dumpServer) {
2526
auto source = (dumpServer) ? &this->serverClassesFile : &this->clientClassesFile;
2627

27-
std::ofstream file(*source, std::ios::out | std::ios::trunc);
28+
auto filepath = fileSystem->FindFileSomewhere(*source).value_or(*source);
29+
std::ofstream file(filepath, std::ios::out | std::ios::trunc);
2830
if (!file.good()) {
2931
console->Warning("Failed to create file!\n");
3032
return file.close();

src/Features/ConfigPlus.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525

2626
static std::map<std::string, std::string> g_svars;
2727
static std::unordered_set<std::string> g_persistentSvars;
28+
static std::string g_persistentSvarsFile;
2829

2930
ON_INIT {
30-
std::ifstream file(PERSISTENT_SVAR_FILENAME);
31+
g_persistentSvarsFile = fileSystem->FindFileSomewhere(PERSISTENT_SVAR_FILENAME).value_or(PERSISTENT_SVAR_FILENAME);
32+
std::ifstream file(g_persistentSvarsFile.c_str());
3133

3234
std::string line;
3335
std::getline(file, line);
@@ -46,7 +48,7 @@ ON_INIT {
4648
}
4749

4850
static void SavePersistentSvars() {
49-
FILE *fp = fopen(PERSISTENT_SVAR_FILENAME, "w");
51+
FILE *fp = fopen(g_persistentSvarsFile.c_str(), "w");
5052
if (fp) {
5153
for (auto &name : g_persistentSvars) {
5254
auto val = g_svars[name];
@@ -68,12 +70,7 @@ CON_COMMAND_F(sar_download_file, "sar_download_file <url> <filepath> [directory]
6870
std::string filepath = args[2];
6971
std::string gamedir = args.ArgC() > 3 ? args[3] : "";
7072

71-
auto result = fileSystem->FindFileSomewhere(filepath, gamedir);
72-
if (result.has_value()) {
73-
filepath = result.value();
74-
} else {
75-
filepath = std::string(engine->GetGameDirectory()) + "/" + filepath;
76-
}
73+
filepath = fileSystem->FindFileSomewhere(filepath, gamedir).value_or(std::string(engine->GetGameDirectory()) + "/" + filepath);
7774

7875
CURL *curl = curl_easy_init();
7976
FILE *fp;

src/Features/DataMapDumper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Modules/Client.hpp"
44
#include "Modules/Console.hpp"
5+
#include "Modules/FileSystem.hpp"
56
#include "Modules/Server.hpp"
67
#include "SAR.hpp"
78
#include "Utils/Memory.hpp"
@@ -37,7 +38,8 @@ DataMapDumper::DataMapDumper()
3738
void DataMapDumper::Dump(bool dumpServer) {
3839
auto source = (dumpServer) ? &this->serverDataMapFile : &this->clientDataMapFile;
3940

40-
std::ofstream file(*source, std::ios::out | std::ios::trunc);
41+
auto filepath = fileSystem->FindFileSomewhere(*source).value_or(*source);
42+
std::ofstream file(filepath, std::ios::out | std::ios::trunc);
4143
if (!file.good()) {
4244
console->Warning("Failed to create file!\n");
4345
return file.close();

src/Features/Demo/DemoGhostPlayer.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "Features/Session.hpp"
77
#include "Modules/Client.hpp"
88
#include "Modules/Engine.hpp"
9+
#include "Modules/FileSystem.hpp"
910
#include "Modules/Server.hpp"
1011
#include "NetworkGhostPlayer.hpp"
1112
#include "Utils.hpp"
@@ -199,18 +200,19 @@ std::string DemoGhostPlayer::CustomDataToString(std::optional<int> slot) {
199200
return Utils::ssprintf("%d", slot);
200201
}
201202

202-
DECL_COMMAND_FILE_COMPLETION(ghost_set_demo, ".dem", engine->GetGameDirectory(), 1);
203+
DECL_COMMAND_FILE_COMPLETION(ghost_set_demo, ".dem", "", 1);
203204
CON_COMMAND_F_COMPLETION(ghost_set_demo, "ghost_set_demo <demo> [ID] - ghost will use this demo. If ID is specified, will create or modify the ID-th ghost\n", 0, AUTOCOMPLETION_FUNCTION(ghost_set_demo)) {
204205
if (args.ArgC() < 2) {
205206
return console->Print(ghost_set_demo.ThisPtr()->m_pszHelpString);
206207
}
207208

208209
sf::Uint32 ID = args.ArgC() > 2 ? std::atoi(args[2]) : 0;
209210
demoGhostPlayer.DeleteGhostsByID(ID);
210-
if (demoGhostPlayer.SetupGhostFromDemo(engine->GetGameDirectory() + std::string("/") + args[1], ID, false)) {
211+
auto filepath = fileSystem->FindFileSomewhere(args[1]).value_or(engine->GetGameDirectory() + std::string("/") + args[1]);
212+
if (demoGhostPlayer.SetupGhostFromDemo(filepath, ID, false)) {
211213
console->Print("Ghost successfully created! Final time of the ghost: %s\n", SpeedrunTimer::Format(demoGhostPlayer.GetGhostByID(ID)->GetTotalTime()).c_str());
212214
} else {
213-
console->Print("Could not parse \"%s\"!\n", (engine->GetGameDirectory() + std::string("/") + args[1]).c_str());
215+
console->Print("Could not parse \"%s\"!\n", args[1]);
214216
}
215217

216218
demoGhostPlayer.UpdateGhostsSameMap();
@@ -232,20 +234,20 @@ CON_COMMAND_F_COMPLETION(ghost_set_demos,
232234
sf::Uint32 ID = args.ArgC() > 3 ? std::atoi(args[3]) : 0;
233235
demoGhostPlayer.DeleteGhostsByID(ID);
234236

235-
auto dir = engine->GetGameDirectory() + std::string("/") + args[1];
237+
auto filepath = fileSystem->FindFileSomewhere(args[1]).value_or(engine->GetGameDirectory() + std::string("/") + args[1]);
236238
int counter = firstDemoId > 1 ? firstDemoId : 2;
237239

238240
bool ok = true;
239241

240242
if (firstDemoId < 2) {
241-
ok = std::filesystem::exists(dir + ".dem");
242-
if (!ok || !demoGhostPlayer.SetupGhostFromDemo(dir, ID, true)) {
243-
return console->Print("Could not parse \"%s\"!\n", (engine->GetGameDirectory() + std::string("/") + args[1]).c_str());
243+
ok = std::filesystem::exists(filepath + ".dem");
244+
if (!ok || !demoGhostPlayer.SetupGhostFromDemo(filepath, ID, true)) {
245+
return console->Print("Could not parse \"%s\"!\n", filepath.c_str());
244246
}
245247
}
246248

247249
while (ok) {
248-
auto tmp_dir = dir + "_" + std::to_string(counter) + ".dem";
250+
auto tmp_dir = filepath + "_" + std::to_string(counter) + ".dem";
249251
ok = std::filesystem::exists(tmp_dir);
250252
if (ok && !demoGhostPlayer.SetupGhostFromDemo(tmp_dir, ID, true)) {
251253
return console->Print("Could not parse \"%s\"!\n", tmp_dir.c_str());

src/Features/Demo/DemoParser.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "Features/Hud/Hud.hpp"
77
#include "Modules/Console.hpp"
88
#include "Modules/Engine.hpp"
9+
#include "Modules/FileSystem.hpp"
910
#include "Variable.hpp"
1011

1112
#include <fstream>
@@ -303,7 +304,7 @@ bool DemoParser::Parse(std::string filePath, Demo *demo, bool ghostRequest, std:
303304

304305
// Commands
305306

306-
DECL_COMMAND_FILE_COMPLETION(sar_time_demo, ".dem", engine->GetGameDirectory(), 1);
307+
DECL_COMMAND_FILE_COMPLETION(sar_time_demo, ".dem", "", 1);
307308
CON_COMMAND_F_COMPLETION(sar_time_demo, "sar_time_demo <demo_name> - parses a demo and prints some information about it\n", 0, AUTOCOMPLETION_FUNCTION(sar_time_demo)) {
308309
if (args.ArgC() != 2) {
309310
return console->Print(sar_time_demo.ThisPtr()->m_pszHelpString);
@@ -324,7 +325,7 @@ CON_COMMAND_F_COMPLETION(sar_time_demo, "sar_time_demo <demo_name> - parses a de
324325
parser.outputMode = sar_time_demo_dev.GetInt();
325326

326327
Demo demo;
327-
auto dir = std::string(engine->GetGameDirectory()) + std::string("/") + name;
328+
auto dir = fileSystem->FindFileSomewhere(name).value_or(name);
328329
if (parser.Parse(dir, &demo)) {
329330
parser.Adjust(&demo);
330331
console->Print("Demo: %s\n", name.c_str());
@@ -351,12 +352,12 @@ CON_COMMAND_F_COMPLETION(sar_time_demos, "sar_time_demos <demo_name> [demo_name2
351352
parser.outputMode = sar_time_demo_dev.GetInt();
352353

353354
auto name = std::string();
354-
auto dir = std::string(engine->GetGameDirectory()) + std::string("/");
355355
for (auto i = 1; i < args.ArgC(); ++i) {
356356
name = std::string(args[i]);
357357

358358
Demo demo;
359-
if (parser.Parse(dir + name, &demo)) {
359+
auto filepath = fileSystem->FindFileSomewhere(name).value_or(name);
360+
if (parser.Parse(filepath, &demo)) {
360361
parser.Adjust(&demo);
361362
console->Print("Demo: %s\n", name.c_str());
362363
console->Print("Client: %s\n", demo.clientName);

0 commit comments

Comments
 (0)