20[[nodiscard]] std::string getAppInstallDir(
AppID appID) {
28 const auto gamePath = ::getAppInstallDir(appID);
29 if (gamePath.empty()) {
32 return load((std::filesystem::path{gamePath} / gameID).
string(), options);
36 if (!std::filesystem::exists(std::filesystem::path{gamePath} /
"gameinfo.txt") || !std::filesystem::is_regular_file(std::filesystem::path{gamePath} /
"gameinfo.txt")) {
43 : rootPath(std::filesystem::path{gamePath}.parent_path().string()) {
45 const auto gameID = std::filesystem::path{gamePath}.filename().string();
48 KV1 gameinfo{
fs::readFileText((std::filesystem::path{gamePath} /
"gameinfo.txt").
string())};
49 if (gameinfo.getChildCount() == 0) {
54 const auto& searchPathKVs = gameinfo[0][
"FileSystem"][
"SearchPaths"];
55 if (searchPathKVs.isInvalid()) {
58 for (
int i = 0; i < searchPathKVs.getChildCount(); i++) {
63 static constexpr std::string_view ALL_SOURCE_ENGINE_PATHS =
"|all_source_engine_paths|";
64 static constexpr std::string_view GAMEINFO_PATH =
"|gameinfo_path|";
65 if (path.starts_with(ALL_SOURCE_ENGINE_PATHS)) {
66 path = path.substr(ALL_SOURCE_ENGINE_PATHS.length());
67 }
else if (path.starts_with(GAMEINFO_PATH)) {
68 path = gameID +
'/' + path.substr(GAMEINFO_PATH.length());
70 if (path.ends_with(
".") && !path.ends_with(
"..")) {
75 if (path.ends_with(
".vpk")) {
76 auto fullPath = this->rootPath +
'/' + path;
79 if (!std::filesystem::exists(fullPath)) {
80 auto fullPathWithDir = (std::filesystem::path{fullPath}.parent_path() / std::filesystem::path{fullPath}.stem()).
string() +
"_dir.vpk";
81 if (!std::filesystem::exists(fullPathWithDir)) {
84 fullPath = fullPathWithDir;
88 for (
const auto& search : searches) {
89 if (!this->searchPathVPKs.contains(search)) {
90 this->searchPathVPKs[search] = std::vector<std::unique_ptr<PackFile>>{};
94 this->searchPathVPKs[search].push_back(std::move(packFile));
98 for (
const auto& search : searches) {
99 if (!this->searchPathDirs.contains(search)) {
100 this->searchPathDirs[search] = {};
102 if (path.ends_with(
"/*")) {
104 if (
const auto globParentPath = this->rootPath +
'/' + path.substr(0, path.length() - 2); std::filesystem::exists(globParentPath) && std::filesystem::is_directory(globParentPath)) {
105 for (
const auto directoryIterator : std::filesystem::directory_iterator{globParentPath, std::filesystem::directory_options::skip_permission_denied}) {
106 auto globChildPath = std::filesystem::relative(directoryIterator.path(), this->rootPath).string();
108 this->searchPathDirs[search].push_back(globChildPath);
111 }
else if (std::filesystem::exists(this->rootPath +
'/' + path)) {
113 this->searchPathDirs[search].push_back(path);
115 if (search ==
"game") {
117 if (!this->searchPathDirs.contains(
"gamebin")) {
118 this->searchPathDirs[
"gamebin"] = {};
120 this->searchPathDirs[
"gamebin"].push_back(path +
"/bin");
124 if (!this->searchPathDirs.contains(
"mod")) {
125 this->searchPathDirs[
"mod"] = {};
127 this->searchPathDirs[
"mod"].push_back(path);
140 if (!this->searchPathDirs.contains(
"executable_path")) {
141 if (!options.
binPlatform.empty() && std::filesystem::exists(std::filesystem::path{this->rootPath} /
"bin" / options.
binPlatform)) {
142 this->searchPathDirs[
"executable_path"] = {
"bin/" + options.
binPlatform};
144 this->searchPathDirs[
"executable_path"] = {};
146 this->searchPathDirs[
"executable_path"].push_back(
"bin");
147 this->searchPathDirs[
"executable_path"].push_back(
"");
151 if (!this->searchPathDirs.contains(
"platform")) {
152 this->searchPathDirs[
"platform"] = {
"platform"};
156 if (this->searchPathDirs.contains(
"game")) {
157 bool foundPlatform =
false;
158 for (
const auto& path : this->searchPathDirs[
"game"]) {
159 if (path ==
"platform") {
160 foundPlatform =
true;
163 if (!foundPlatform) {
164 this->searchPathDirs[
"game"].push_back(
"platform");
169 if (!this->searchPathDirs.contains(
"default_write_path")) {
170 this->searchPathDirs[
"default_write_path"] = {gameID};
174 if (!this->searchPathDirs.contains(
"logdir")) {
175 this->searchPathDirs[
"logdir"] = {gameID};
179 if (!this->searchPathDirs.contains(
"config")) {
180 this->searchPathDirs[
"config"] = {
"platform/config"};
185 return this->searchPathDirs;
189 return this->searchPathDirs;
193 return this->searchPathVPKs;
197 return this->searchPathVPKs;
200std::optional<std::vector<std::byte>>
FileSystem::read(std::string_view filePath, std::string_view searchPath,
bool prioritizeVPKs)
const {
205 const auto checkVPKs = [
this, &filePathStr, &searchPathStr]() -> std::optional<std::vector<std::byte>> {
206 if (!this->searchPathVPKs.contains(searchPathStr)) {
209 for (
const auto& packFile : this->searchPathVPKs.at(searchPathStr)) {
210 if (packFile->hasEntry(filePathStr)) {
211 return packFile->readEntry(filePathStr);
217 if (prioritizeVPKs) {
218 if (
auto data = checkVPKs()) {
223 if (this->searchPathDirs.contains(searchPathStr)) {
224 for (
const auto& basePath : this->searchPathDirs.at(searchPathStr)) {
226 if (
const auto testPath = this->rootPath +
'/' + basePath +
'/' + filePathStr; std::filesystem::exists(testPath)) {
232 if (!prioritizeVPKs) {
238std::optional<std::vector<std::byte>>
FileSystem::readForMap(
const PackFile* map, std::string_view filePath, std::string_view searchPath,
bool prioritizeVPKs)
const {
239 if (
const auto filePathStr = std::string{filePath}; map && map->
hasEntry(filePathStr)) {
242 return this->
read(filePath, searchPath, prioritizeVPKs);
static std::optional< FileSystem > load(steampp::AppID appID, std::string_view gameID, const FileSystemOptions &options={})
Creates a FileSystem based on a Steam installation.
std::optional< std::vector< std::byte > > readForMap(const vpkpp::PackFile *map, std::string_view filePath, std::string_view searchPath="GAME", bool prioritizeVPKs=true) const
const SearchPathMapVPK & getSearchPathVPKs() const
const SearchPathMapDir & getSearchPathDirs() const
std::unordered_map< std::string, std::vector< std::string > > SearchPathMapDir
std::unordered_map< std::string, std::vector< std::unique_ptr< vpkpp::PackFile > > > SearchPathMapVPK
FileSystem(std::string_view gamePath, const FileSystemOptions &options={})
std::optional< std::vector< std::byte > > read(std::string_view filePath, std::string_view searchPath="GAME", bool prioritizeVPKs=true) const
std::string getAppInstallDir(AppID appID) const
bool hasEntry(const std::string &path, bool includeUnbaked=true) const
Check if an entry exists given the file path.
virtual std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const =0
Try to read the entry's data to a bytebuffer.
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a generic pack file. The parser is selected based on the file extension.
std::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
std::string readFileText(const std::string &filepath, std::size_t startOffset=0)
void normalizeSlashes(std::string &path, bool stripSlashPrefix=false, bool stripSlashSuffix=true)
std::vector< std::string > split(std::string_view s, char delim)
void toLower(std::string &input)
Based on SteamAppPathProvider.