Skip to content

Commit

Permalink
wip(fspp): start work on filesystem library
Browse files Browse the repository at this point in the history
  • Loading branch information
craftablescience committed Jul 1, 2024
1 parent 28bbdbe commit a153634
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 1 deletion.
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Options
option(SOURCEPP_USE_DMXPP "Build dmxpp library" ON)
option(SOURCEPP_USE_FGDPP "Build fgdpp library" ON)
option(SOURCEPP_USE_FSPP "Build fspp library" ON)
option(SOURCEPP_USE_KVPP "Build kvpp library" ON)
option(SOURCEPP_USE_MDLPP "Build mdlpp library" ON)
option(SOURCEPP_USE_STEAMPP "Build steampp library" ON)
Expand All @@ -28,9 +29,13 @@ option(SOURCEPP_USE_STATIC_MSVC_RUNTIME "Link to static MSVC runtime library"


# Option overrides
if(SOURCEPP_USE_STEAMPP OR SOURCEPP_USE_VPKPP)
if(SOURCEPP_USE_FSPP OR SOURCEPP_USE_STEAMPP OR SOURCEPP_USE_VPKPP)
set(SOURCEPP_USE_KVPP ON CACHE INTERNAL "")
endif()
if(SOURCEPP_USE_FSPP)
set(SOURCEPP_USE_STEAMPP ON CACHE INTERNAL "")
set(SOURCEPP_USE_VPKPP ON CACHE INTERNAL "")
endif()


# Options per-library
Expand Down Expand Up @@ -86,6 +91,7 @@ endif()
# Add libraries
add_sourcepp_library(dmxpp)
add_sourcepp_library(fgdpp)
add_sourcepp_library(fspp)
add_sourcepp_library(kvpp)
add_sourcepp_library(mdlpp)
add_sourcepp_library(steampp)
Expand Down
54 changes: 54 additions & 0 deletions include/fspp/fspp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once

#include <optional>

#include <steampp/steampp.h>

namespace fspp {

struct FileSystemOptions {
std::string language;
bool prioritizeVPKs = true;
bool loadAddonList = false;
bool useDLCFolders = false;
bool useUpdate = true;
bool useXLSPPatch = true;
};

class FileSystem {
public:
using SearchPathMap = std::unordered_map<std::string, std::vector<std::string>>;

/**
* Creates a FileSystem based on a Steam installation
* @param appID The AppID of the base game
* @param gameName The name of the directory where gameinfo.txt is located (e.g. "portal2")
* @param options FileSystem creation options
* @return The created FileSystem if the specified Steam game is installed
*/
[[nodiscard]] static std::optional<FileSystem> load(steampp::AppID appID, std::string_view gameName, const FileSystemOptions& options = {});

/**
* Creates a FileSystem based on a local installation
* @param gamePath The full path to the directory where gameinfo.txt is located (e.g. "path/to/portal2")
* @param options FileSystem creation options
* @return The created FileSystem if gameinfo.txt is found
*/
[[nodiscard]] static std::optional<FileSystem> load(std::string_view gamePath, const FileSystemOptions& options = {});

[[nodiscard]] std::vector<std::string> getSearchPaths() const;

[[nodiscard]] const std::vector<std::string>& getPathsForSearchPath(std::string_view searchPath) const;

[[nodiscard]] const SearchPathMap& getSearchPathData() const;

[[nodiscard]] std::optional<std::vector<std::byte>> read(std::string_view filePath, std::string_view searchPath = "GAME") const;

protected:
explicit FileSystem(std::string_view gamePath, const FileSystemOptions& options = {});

private:
SearchPathMap searchPaths;
};

} // namespace fspp
3 changes: 3 additions & 0 deletions src/fspp/_fspp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
add_pretty_parser(fspp DEPS kvpp steampp vpkpp SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/include/fspp/fspp.h"
"${CMAKE_CURRENT_LIST_DIR}/fspp.cpp")
144 changes: 144 additions & 0 deletions src/fspp/fspp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <fspp/fspp.h>

#include <filesystem>
#include <ranges>

#include <kvpp/kvpp.h>
#include <sourcepp/fs/FS.h>
#include <sourcepp/string/String.h>

using namespace fspp;
using namespace kvpp;
using namespace sourcepp;
using namespace steampp;

namespace {

#if defined(_WIN32)
constexpr std::string_view FS_PLATFORM = "win64";
#elif defined(__APPLE__)
constexpr std::string_view FS_PLATFORM = "osx64";
#elif defined(__linux__)
constexpr std::string_view FS_PLATFORM = "linux64";
#else
#error "Unknown platform!"
#endif

std::string getAppInstallDir(AppID appID) {
static Steam steam;
return steam.getAppInstallDir(appID);
}

} // namespace

std::optional<FileSystem> FileSystem::load(steampp::AppID appID, std::string_view gameName, const FileSystemOptions& options) {
auto gamePath = ::getAppInstallDir(appID);
if (gamePath.empty()) {
return std::nullopt;
}
return load((std::filesystem::path{gamePath} / gameName).string(), options);
}

std::optional<FileSystem> FileSystem::load(std::string_view gamePath, const FileSystemOptions& options) {
if (!std::filesystem::exists(std::filesystem::path{gamePath} / "gameinfo.txt") || !std::filesystem::is_regular_file(std::filesystem::path{gamePath} / "gameinfo.txt")) {
return std::nullopt;
}
return FileSystem{gamePath, options};
}

FileSystem::FileSystem(std::string_view gamePath, const FileSystemOptions& options) {
SearchPathMap dirSearchPaths;
SearchPathMap vpkSearchPaths;

// Load paths from gameinfo.txt
KV1 gameinfo{fs::readFileText((std::filesystem::path{gamePath} / "gameinfo.txt").string())};
if (gameinfo.getChildCount() == 0) {
return;
}
const auto& searchPathKVs = gameinfo[0]["FileSystem"]["SearchPaths"];
if (searchPathKVs.isInvalid()) {
return;
}
for (int i = 0; i < searchPathKVs.getChildCount(); i++) {
auto searches = string::split(searchPathKVs.getKey(), '+');
auto path = std::string{searchPathKVs[i].getValue()};

// Replace |all_source_engine_paths| with <root>/, |gameinfo_path| with <root>/<game>/
static constexpr std::string_view ALL_SOURCE_ENGINE_PATHS = "|all_source_engine_paths|";
static constexpr std::string_view GAMEINFO_PATH = "|gameinfo_path|";
if (path.starts_with(ALL_SOURCE_ENGINE_PATHS)) {
path = (std::filesystem::path{gamePath} / ".." / path.substr(ALL_SOURCE_ENGINE_PATHS.length())).string();
} else if (path.starts_with(GAMEINFO_PATH)) {
path = (std::filesystem::path{gamePath} / path.substr(GAMEINFO_PATH.length())).string();
}

if (path.ends_with(".vpk")) {
// Normalize the ending (add _dir if present)
if (!std::filesystem::exists(path)) {
auto pathWithDir = (std::filesystem::path{path}.parent_path() / std::filesystem::path{path}.stem()).string() + "_dir.vpk";
if (!std::filesystem::exists(pathWithDir)) {
continue;
}
path = pathWithDir;
}

for (const auto& search : searches) {
if (!vpkSearchPaths.contains(search)) {
vpkSearchPaths[search] = {};
}
vpkSearchPaths[search].push_back(path);
}
} else {
for (const auto& search : searches) {
if (!dirSearchPaths.contains(search)) {
dirSearchPaths[search] = {};
}
dirSearchPaths[search].push_back(path);
}
}
}

// Add DLCs / update dir / xlsppatch dir if they exist

// Add EXECUTABLE_PATH if it doesn't exist, point it at <root>/bin/<platform>/;<root>/bin/;<root>/

// Add PLATFORM if it doesn't exist, point it at <root>/platform/

// Add DEFAULT_WRITE_PATH, LOGDIR if they doesn't exist, point them at <root>/<game>/

// Add CONFIG if it doesn't exist, point it at <root>/platform/config/

// Merge dir/vpk search paths together
const auto* firstSearchPathsMap = options.prioritizeVPKs ? &vpkSearchPaths : &dirSearchPaths;
const auto* secondSearchPathsMap = options.prioritizeVPKs ? &dirSearchPaths : &vpkSearchPaths;
for (const auto& [search, paths] : *firstSearchPathsMap) {
this->searchPaths[search] = paths;
}
for (const auto& [search, paths] : *secondSearchPathsMap) {
if (this->searchPaths.contains(search)) {
// insert
} else {
this->searchPaths[search] = paths;
}
}
}

std::vector<std::string> FileSystem::getSearchPaths() const {
auto keys = std::views::keys(this->searchPaths);
return {keys.begin(), keys.end()};
}

const std::vector<std::string>& FileSystem::getPathsForSearchPath(std::string_view searchPath) const {
return this->searchPaths.at(std::string{searchPath});
}

const FileSystem::SearchPathMap& FileSystem::getSearchPathData() const {
return this->searchPaths;
}

std::optional<std::vector<std::byte>> FileSystem::read(std::string_view filePath, std::string_view searchPath) const {
if (!this->searchPaths.contains(std::string{searchPath})) {
return std::nullopt;
}
return std::nullopt;
}
15 changes: 15 additions & 0 deletions test/fspp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <gtest/gtest.h>

#include <fspp/fspp.h>

using namespace fspp;
using namespace sourcepp;

#if 0

TEST(fspp, open_portal2) {
auto fs = FileSystem::load(620, "portal2");
ASSERT_TRUE(fs);
}

#endif

0 comments on commit a153634

Please sign in to comment.