Skip to content

Commit b5ef659

Browse files
wip(fspp): start work on filesystem library
1 parent 4b06b2e commit b5ef659

File tree

5 files changed

+223
-1
lines changed

5 files changed

+223
-1
lines changed

CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
# Options
88
option(SOURCEPP_USE_DMXPP "Build dmxpp library" ON)
99
option(SOURCEPP_USE_FGDPP "Build fgdpp library" ON)
10+
option(SOURCEPP_USE_FSPP "Build fspp library" ON)
1011
option(SOURCEPP_USE_KVPP "Build kvpp library" ON)
1112
option(SOURCEPP_USE_MDLPP "Build mdlpp library" ON)
1213
option(SOURCEPP_USE_STEAMPP "Build steampp library" ON)
@@ -19,9 +20,13 @@ option(SOURCEPP_USE_STATIC_MSVC_RUNTIME "Link to static MSVC runtime library"
1920

2021

2122
# Option overrides
22-
if(SOURCEPP_USE_STEAMPP OR SOURCEPP_USE_VPKPP)
23+
if(SOURCEPP_USE_FSPP OR SOURCEPP_USE_STEAMPP OR SOURCEPP_USE_VPKPP)
2324
set(SOURCEPP_USE_KVPP ON CACHE INTERNAL "")
2425
endif()
26+
if(SOURCEPP_USE_FSPP)
27+
set(SOURCEPP_USE_STEAMPP ON CACHE INTERNAL "")
28+
set(SOURCEPP_USE_VPKPP ON CACHE INTERNAL "")
29+
endif()
2530

2631

2732
# Options per-library
@@ -77,6 +82,7 @@ endif()
7782
# Add libraries
7883
add_sourcepp_library(dmxpp)
7984
add_sourcepp_library(fgdpp)
85+
add_sourcepp_library(fspp)
8086
add_sourcepp_library(kvpp)
8187
add_sourcepp_library(mdlpp)
8288
add_sourcepp_library(steampp)

include/fspp/fspp.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#pragma once
2+
3+
#include <optional>
4+
5+
#include <steampp/steampp.h>
6+
7+
namespace fspp {
8+
9+
struct FileSystemOptions {
10+
std::string language;
11+
bool prioritizeVPKs = true;
12+
bool loadAddonList = false;
13+
bool useDLCFolders = false;
14+
bool useUpdate = true;
15+
bool useXLSPPatch = true;
16+
};
17+
18+
class FileSystem {
19+
public:
20+
using SearchPathMap = std::unordered_map<std::string, std::vector<std::string>>;
21+
22+
/**
23+
* Creates a FileSystem based on a Steam installation
24+
* @param appID The AppID of the base game
25+
* @param gameName The name of the directory where gameinfo.txt is located (e.g. "portal2")
26+
* @param options FileSystem creation options
27+
* @return The created FileSystem if the specified Steam game is installed
28+
*/
29+
[[nodiscard]] static std::optional<FileSystem> load(steampp::AppID appID, std::string_view gameName, const FileSystemOptions& options = {});
30+
31+
/**
32+
* Creates a FileSystem based on a local installation
33+
* @param gamePath The full path to the directory where gameinfo.txt is located (e.g. "path/to/portal2")
34+
* @param options FileSystem creation options
35+
* @return The created FileSystem if gameinfo.txt is found
36+
*/
37+
[[nodiscard]] static std::optional<FileSystem> load(std::string_view gamePath, const FileSystemOptions& options = {});
38+
39+
[[nodiscard]] std::vector<std::string> getSearchPaths() const;
40+
41+
[[nodiscard]] const std::vector<std::string>& getPathsForSearchPath(std::string_view searchPath) const;
42+
43+
[[nodiscard]] const SearchPathMap& getSearchPathData() const;
44+
45+
[[nodiscard]] std::optional<std::vector<std::byte>> read(std::string_view filePath, std::string_view searchPath = "GAME") const;
46+
47+
protected:
48+
explicit FileSystem(std::string_view gamePath, const FileSystemOptions& options = {});
49+
50+
private:
51+
SearchPathMap searchPaths;
52+
};
53+
54+
} // namespace fspp

src/fspp/_fspp.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
add_pretty_parser(fspp DEPS kvpp steampp vpkpp SOURCES
2+
"${CMAKE_CURRENT_SOURCE_DIR}/include/fspp/fspp.h"
3+
"${CMAKE_CURRENT_LIST_DIR}/fspp.cpp")

src/fspp/fspp.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#include <fspp/fspp.h>
2+
3+
#include <filesystem>
4+
#include <ranges>
5+
6+
#include <kvpp/kvpp.h>
7+
#include <sourcepp/fs/FS.h>
8+
#include <sourcepp/string/String.h>
9+
10+
using namespace fspp;
11+
using namespace kvpp;
12+
using namespace sourcepp;
13+
using namespace steampp;
14+
15+
namespace {
16+
17+
#if defined(_WIN32)
18+
constexpr std::string_view FS_PLATFORM = "win64";
19+
#elif defined(__APPLE__)
20+
constexpr std::string_view FS_PLATFORM = "osx64";
21+
#elif defined(__linux__)
22+
constexpr std::string_view FS_PLATFORM = "linux64";
23+
#else
24+
#error "Unknown platform!"
25+
#endif
26+
27+
std::string getAppInstallDir(AppID appID) {
28+
static Steam steam;
29+
return steam.getAppInstallDir(appID);
30+
}
31+
32+
} // namespace
33+
34+
std::optional<FileSystem> FileSystem::load(steampp::AppID appID, std::string_view gameName, const FileSystemOptions& options) {
35+
auto gamePath = ::getAppInstallDir(appID);
36+
if (gamePath.empty()) {
37+
return std::nullopt;
38+
}
39+
return load((std::filesystem::path{gamePath} / gameName).string(), options);
40+
}
41+
42+
std::optional<FileSystem> FileSystem::load(std::string_view gamePath, const FileSystemOptions& options) {
43+
if (!std::filesystem::exists(std::filesystem::path{gamePath} / "gameinfo.txt") || !std::filesystem::is_regular_file(std::filesystem::path{gamePath} / "gameinfo.txt")) {
44+
return std::nullopt;
45+
}
46+
return FileSystem{gamePath, options};
47+
}
48+
49+
FileSystem::FileSystem(std::string_view gamePath, const FileSystemOptions& options) {
50+
SearchPathMap dirSearchPaths;
51+
SearchPathMap vpkSearchPaths;
52+
53+
// Load paths from gameinfo.txt
54+
KV1 gameinfo{fs::readFileText((std::filesystem::path{gamePath} / "gameinfo.txt").string())};
55+
if (gameinfo.getChildCount() == 0) {
56+
return;
57+
}
58+
const auto& searchPathKVs = gameinfo[0]["FileSystem"]["SearchPaths"];
59+
if (searchPathKVs.isInvalid()) {
60+
return;
61+
}
62+
for (int i = 0; i < searchPathKVs.getChildCount(); i++) {
63+
auto searches = string::split(searchPathKVs.getKey(), '+');
64+
auto path = std::string{searchPathKVs[i].getValue()};
65+
66+
// Replace |all_source_engine_paths| with <root>/, |gameinfo_path| with <root>/<game>/
67+
static constexpr std::string_view ALL_SOURCE_ENGINE_PATHS = "|all_source_engine_paths|";
68+
static constexpr std::string_view GAMEINFO_PATH = "|gameinfo_path|";
69+
if (path.starts_with(ALL_SOURCE_ENGINE_PATHS)) {
70+
path = (std::filesystem::path{gamePath} / ".." / path.substr(ALL_SOURCE_ENGINE_PATHS.length())).string();
71+
} else if (path.starts_with(GAMEINFO_PATH)) {
72+
path = (std::filesystem::path{gamePath} / path.substr(GAMEINFO_PATH.length())).string();
73+
}
74+
75+
if (path.ends_with(".vpk")) {
76+
// Normalize the ending (add _dir if present)
77+
if (!std::filesystem::exists(path)) {
78+
auto pathWithDir = (std::filesystem::path{path}.parent_path() / std::filesystem::path{path}.stem()).string() + "_dir.vpk";
79+
if (!std::filesystem::exists(pathWithDir)) {
80+
continue;
81+
}
82+
path = pathWithDir;
83+
}
84+
85+
for (const auto& search : searches) {
86+
if (!vpkSearchPaths.contains(search)) {
87+
vpkSearchPaths[search] = {};
88+
}
89+
vpkSearchPaths[search].push_back(path);
90+
}
91+
} else {
92+
for (const auto& search : searches) {
93+
if (!dirSearchPaths.contains(search)) {
94+
dirSearchPaths[search] = {};
95+
}
96+
dirSearchPaths[search].push_back(path);
97+
}
98+
}
99+
}
100+
101+
// Add DLCs / update dir / xlsppatch dir if they exist
102+
103+
// Add EXECUTABLE_PATH if it doesn't exist, point it at <root>/bin/<platform>/;<root>/bin/;<root>/
104+
105+
// Add PLATFORM if it doesn't exist, point it at <root>/platform/
106+
107+
// Add DEFAULT_WRITE_PATH, LOGDIR if they doesn't exist, point them at <root>/<game>/
108+
109+
// Add CONFIG if it doesn't exist, point it at <root>/platform/config/
110+
111+
// Merge dir/vpk search paths together
112+
const auto* firstSearchPathsMap = options.prioritizeVPKs ? &vpkSearchPaths : &dirSearchPaths;
113+
const auto* secondSearchPathsMap = options.prioritizeVPKs ? &dirSearchPaths : &vpkSearchPaths;
114+
for (const auto& [search, paths] : *firstSearchPathsMap) {
115+
this->searchPaths[search] = paths;
116+
}
117+
for (const auto& [search, paths] : *secondSearchPathsMap) {
118+
if (this->searchPaths.contains(search)) {
119+
// insert
120+
} else {
121+
this->searchPaths[search] = paths;
122+
}
123+
}
124+
}
125+
126+
std::vector<std::string> FileSystem::getSearchPaths() const {
127+
auto keys = std::views::keys(this->searchPaths);
128+
return {keys.begin(), keys.end()};
129+
}
130+
131+
const std::vector<std::string>& FileSystem::getPathsForSearchPath(std::string_view searchPath) const {
132+
return this->searchPaths.at(std::string{searchPath});
133+
}
134+
135+
const FileSystem::SearchPathMap& FileSystem::getSearchPathData() const {
136+
return this->searchPaths;
137+
}
138+
139+
std::optional<std::vector<std::byte>> FileSystem::read(std::string_view filePath, std::string_view searchPath) const {
140+
if (!this->searchPaths.contains(std::string{searchPath})) {
141+
return std::nullopt;
142+
}
143+
return std::nullopt;
144+
}

test/fspp.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <fspp/fspp.h>
4+
5+
using namespace fspp;
6+
using namespace sourcepp;
7+
8+
#if 1
9+
10+
TEST(fspp, open_portal2) {
11+
auto fs = FileSystem::load(620, "portal2");
12+
ASSERT_TRUE(fs);
13+
}
14+
15+
#endif

0 commit comments

Comments
 (0)