Skip to content

Commit 84b3485

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 0a65fcf commit 84b3485

25 files changed

Lines changed: 209 additions & 149 deletions

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

@@ -221,15 +222,32 @@ int _FileCompletionFunc(std::string extension, std::string rootdir, int exp_args
221222
try {
222223
std::set<std::string> sorted;
223224

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

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
@@ -872,12 +872,9 @@ CON_COMMAND(sar_cam_path_export,
872872
if (rate <= 0) rate = 60;
873873

874874
// check if file exists before writing
875-
std::ifstream testFile(filename.c_str());
876-
if (testFile.good()) {
877-
testFile.close();
875+
if (std::filesystem::exists(filename)) {
878876
return console->Print("File \"%s\" exists and cannot be overwritten.\n", filename.c_str());
879877
}
880-
testFile.close();
881878

882879
std::ofstream file(filename.c_str());
883880

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: 4 additions & 2 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];

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: 13 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"
@@ -201,18 +202,20 @@ std::string DemoGhostPlayer::CustomDataToString(std::optional<int> slot) {
201202
return Utils::ssprintf("%d", slot.value());
202203
}
203204

204-
DECL_COMMAND_FILE_COMPLETION(ghost_set_demo, ".dem", engine->GetGameDirectory(), 1);
205+
DECL_COMMAND_FILE_COMPLETION(ghost_set_demo, ".dem", "", 1);
205206
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)) {
206207
if (args.ArgC() < 2) {
207208
return console->Print(ghost_set_demo.ThisPtr()->m_pszHelpString);
208209
}
209210

210211
sf::Uint32 ID = args.ArgC() > 2 ? std::atoi(args[2]) : 0;
211212
demoGhostPlayer.DeleteGhostsByID(ID);
212-
if (demoGhostPlayer.SetupGhostFromDemo(engine->GetGameDirectory() + std::string("/") + args[1], ID, false)) {
213+
auto filepath = fileSystem->FindFileSomewhere(std::string(args[1]) + ".dem").value_or(engine->GetGameDirectory() + std::string("/") + args[1] + ".dem");
214+
filepath = filepath.substr(0, filepath.find_last_of('.'));
215+
if (demoGhostPlayer.SetupGhostFromDemo(filepath, ID, false)) {
213216
console->Print("Ghost successfully created! Final time of the ghost: %s\n", SpeedrunTimer::Format(demoGhostPlayer.GetGhostByID(ID)->GetTotalTime()).c_str());
214217
} else {
215-
console->Print("Could not parse \"%s\"!\n", (engine->GetGameDirectory() + std::string("/") + args[1]).c_str());
218+
console->Print("Could not parse \"%s\"!\n", args[1]);
216219
}
217220

218221
demoGhostPlayer.UpdateGhostsSameMap();
@@ -234,20 +237,22 @@ CON_COMMAND_F_COMPLETION(ghost_set_demos,
234237
sf::Uint32 ID = args.ArgC() > 3 ? std::atoi(args[3]) : 0;
235238
demoGhostPlayer.DeleteGhostsByID(ID);
236239

237-
auto dir = engine->GetGameDirectory() + std::string("/") + args[1];
240+
auto dir = std::string(args[1]) + ".dem";
241+
auto filepath = fileSystem->FindFileSomewhere(dir).value_or(std::string(engine->GetGameDirectory()) + "/" + dir);
242+
filepath = filepath.substr(0, filepath.find_last_of('.'));
238243
int counter = firstDemoId > 1 ? firstDemoId : 2;
239244

240245
bool ok = true;
241246

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

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

src/Features/Demo/DemoParser.cpp

Lines changed: 6 additions & 6 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 + ".dem").value_or(name + ".dem");
328329
if (parser.Parse(dir, &demo)) {
329330
parser.Adjust(&demo);
330331
console->Print("Demo: %s\n", name.c_str());
@@ -350,13 +351,12 @@ CON_COMMAND_F_COMPLETION(sar_time_demos, "sar_time_demos <demo_name> [demo_name2
350351
DemoParser parser;
351352
parser.outputMode = sar_time_demo_dev.GetInt();
352353

353-
auto name = std::string();
354-
auto dir = std::string(engine->GetGameDirectory()) + std::string("/");
355354
for (auto i = 1; i < args.ArgC(); ++i) {
356-
name = std::string(args[i]);
355+
auto name = std::string(args[i]);
357356

358357
Demo demo;
359-
if (parser.Parse(dir + name, &demo)) {
358+
auto filepath = fileSystem->FindFileSomewhere(name + ".dem").value_or(name + ".dem");
359+
if (parser.Parse(filepath, &demo)) {
360360
parser.Adjust(&demo);
361361
console->Print("Demo: %s\n", name.c_str());
362362
console->Print("Client: %s\n", demo.clientName);

0 commit comments

Comments
 (0)