Skip to content

Commit 5d90633

Browse files
wip(fspp): start work on filesystem library
1 parent bf221cc commit 5d90633

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
1717
option(SOURCEPP_USE_BSPPP "Build bsppp library" ON)
1818
option(SOURCEPP_USE_DMXPP "Build dmxpp library" ON)
1919
option(SOURCEPP_USE_FGDPP "Build fgdpp library" ON)
20+
option(SOURCEPP_USE_FSPP "Build fspp library" ON)
2021
option(SOURCEPP_USE_KVPP "Build kvpp library" ON)
2122
option(SOURCEPP_USE_MDLPP "Build mdlpp library" ON)
2223
option(SOURCEPP_USE_STEAMPP "Build steampp library" ON)
@@ -29,6 +30,11 @@ option(SOURCEPP_USE_STATIC_MSVC_RUNTIME "Link to static MSVC runtime library"
2930

3031

3132
# Option overrides
33+
if(SOURCEPP_USE_FSPP)
34+
set(SOURCEPP_USE_KVPP ON CACHE INTERNAL "")
35+
set(SOURCEPP_USE_STEAMPP ON CACHE INTERNAL "")
36+
set(SOURCEPP_USE_VPKPP ON CACHE INTERNAL "")
37+
endif()
3238
if(SOURCEPP_USE_STEAMPP)
3339
set(SOURCEPP_USE_KVPP ON CACHE INTERNAL "")
3440
endif()
@@ -88,6 +94,7 @@ endif()
8894
add_sourcepp_library(bsppp NO_TEST)
8995
add_sourcepp_library(dmxpp)
9096
add_sourcepp_library(fgdpp)
97+
add_sourcepp_library(fspp)
9198
add_sourcepp_library(kvpp)
9299
add_sourcepp_library(mdlpp)
93100
add_sourcepp_library(steampp C)

include/fspp/fspp.h

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

src/fspp/_fspp.cmake

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

src/fspp/fspp.cpp

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

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 0
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)