├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .gitmodules
├── .idea
├── .gitignore
├── .name
├── inspectionProfiles
│ └── Project_Default.xml
├── libsteam.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── CMakeLists.txt
├── README.md
├── lib
├── CMakeLists.txt
├── include
│ ├── steam.hpp
│ └── steam
│ │ ├── api
│ │ ├── appid.hpp
│ │ ├── steam_api.hpp
│ │ ├── steam_grid_db.hpp
│ │ └── user.hpp
│ │ ├── file_formats
│ │ ├── keyvalues.hpp
│ │ └── vdf.hpp
│ │ └── helpers
│ │ ├── file.hpp
│ │ ├── fs.hpp
│ │ ├── hash.hpp
│ │ ├── net.hpp
│ │ └── utils.hpp
└── source
│ ├── api
│ ├── steam_api.cpp
│ └── steam_grid_api.cpp
│ ├── file_formats
│ ├── keyvalues.cpp
│ └── vdf.cpp
│ └── helpers
│ ├── file.cpp
│ ├── net.cpp
│ └── utils.cpp
└── test
├── CMakeLists.txt
└── source
└── main.cpp
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | env:
8 | BUILD_TYPE: Release
9 |
10 | jobs:
11 |
12 | linux:
13 | runs-on: ubuntu-20.04
14 | name: 🐧 Ubuntu 20.04
15 | steps:
16 |
17 | - name: Checkout
18 | uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 0
21 | submodules: recursive
22 |
23 | - name: Install deps
24 | run: |
25 | sudo apt update
26 | sudo apt install -y gcc-10 g++-10 cmake make build-essential libcurl4-gnutls-dev
27 |
28 | - name: Build
29 | run: |
30 | mkdir -p build
31 | cd build
32 | CC=gcc-10 CXX=g++-10 cmake -DCMAKE_INSTALL_PREFIX="$PWD/install" ..
33 | make -j4 install
34 |
35 | - name: Upload
36 | uses: actions/upload-artifact@v2
37 | with:
38 | name: ELF
39 | path: |
40 | build/install/*
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | cmake-build-debug/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "external/fmt"]
2 | path = external/fmt
3 | url = https://github.com/fmtlib/fmt
4 | [submodule "https://github.com/nlohmann/json"]
5 | path = https://github.com/nlohmann/json
6 | url = external/json
7 | [submodule "external/json"]
8 | path = external/json
9 | url = https://github.com/nlohmann/json
10 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | main
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/libsteam.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.21)
2 | project(main)
3 |
4 | set(CMAKE_CXX_STANDARD 20)
5 | set(CMAKE_SHARED_LIBRARY_PREFIX "")
6 |
7 | find_package(PkgConfig REQUIRED)
8 | pkg_check_modules(LIBCURL REQUIRED IMPORTED_TARGET libcurl)
9 |
10 | add_subdirectory(external/fmt)
11 | set(JSON_MultipleHeaders ON CACHE BOOL "Enable Multiple Headers" FORCE)
12 | add_subdirectory(external/json)
13 | unset(JSON_MultipleHeaders)
14 | set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON)
15 | set_target_properties(nlohmann_json PROPERTIES POSITION_INDEPENDENT_CODE ON)
16 |
17 | add_subdirectory(lib)
18 | add_subdirectory(test)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # libsteam
2 |
3 | A nice and easy to use C++ library to interact with Steam and its file formats. Mainly intended for the Steam Deck but it will work elsewhere too
4 |
5 | ## Features
6 | - Binary VDF File parser (e.g shortcuts.vdf)
7 | - Parsing
8 | - Modifying fields
9 | - Adding new fields
10 | - Dumping back to binary representation
11 | - Pretty printing
12 | - KeyValue File parser (e.g config.vdf)
13 | - Parsing
14 | - Modifying fields
15 | - Adding new fields
16 | - Dumping back to text representation
17 | - Pretty printing
18 | - Interaction with the Steam Game UI
19 | - Restarting Game UI
20 | - Adding new shortcuts to Steam
21 | - Removing shortcuts from Steam
22 | - Enabling Proton for shortcuts
23 | - Querying the SteamGridDB API
24 | - Searching
25 | - Getting Grids, Heroes, Logos and Icons
26 |
27 | ## Example
28 |
29 | ### shortcut.vdf interaction
30 | ```cpp
31 |
32 | std::vector vdfFileContent = /* ... */;
33 |
34 | steam::VDF vdf(vdfFileContent);
35 |
36 | // Print game name of first entry
37 | auto firstGameName = vdf["shortcuts"]["0"]["AppName"].string();
38 | std::printf("%s", firstGameName.c_str());
39 |
40 | // Hide first game from UI
41 | vdf["shortcuts"]["0"]["IsHidden"] = true;
42 |
43 | // Convert back to binary representation
44 | auto binaryData = vdf.dump();
45 |
46 | // Print vdf as formatted data
47 | std::printf("%s", vdf.format().c_str());
48 | ```
49 |
50 | Check out the [/test](/test) folder for more examples
--------------------------------------------------------------------------------
/lib/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.21)
2 | project(libsteam)
3 |
4 | set(CMAKE_CXX_STANDARD 20)
5 | set(CMAKE_SHARED_LIBRARY_PREFIX "")
6 |
7 | add_library(libsteam SHARED
8 | source/api/steam_api.cpp
9 | source/api/steam_grid_api.cpp
10 |
11 | source/file_formats/vdf.cpp
12 | source/file_formats/keyvalues.cpp
13 |
14 | source/helpers/file.cpp
15 | source/helpers/utils.cpp
16 | source/helpers/net.cpp
17 | )
18 |
19 | set_target_properties(libsteam
20 | PROPERTIES
21 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
22 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
23 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
24 | )
25 |
26 | target_link_libraries(libsteam PUBLIC fmt::fmt nlohmann_json curl)
27 |
28 | target_include_directories(libsteam PUBLIC include)
29 |
30 | install(TARGETS libsteam)
--------------------------------------------------------------------------------
/lib/include/steam.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace steam {
6 |
7 | using u8 = std::uint8_t;
8 | using u16 = std::uint16_t;
9 | using u32 = std::uint32_t;
10 | using u64 = std::uint64_t;
11 | using u128 = __uint128_t;
12 |
13 | using i8 = std::int8_t;
14 | using i16 = std::int16_t;
15 | using i32 = std::int32_t;
16 | using i64 = std::int64_t;
17 | using i128 = __int128_t;
18 |
19 | using f32 = float;
20 | using f64 = double;
21 | using f128 = long double;
22 | }
--------------------------------------------------------------------------------
/lib/include/steam/api/appid.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 |
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 |
13 | namespace steam::api {
14 |
15 | class AppId {
16 | public:
17 | AppId() : m_appId(-1) { }
18 |
19 | AppId(const std::fs::path &exePath, const std::string &appName) noexcept
20 | : m_appId((u64(crc32(exePath.string() + appName) | 0x8000'0000) << 32) | 0x0200'0000) { }
21 |
22 | explicit AppId(u64 appId) : m_appId(appId) { }
23 |
24 | static std::vector getAppIds(const api::User &user) {
25 | auto configPath = fs::getSteamDirectory() / "userdata" / std::to_string(user.getId()) / "config";
26 |
27 | // Open shortcuts file
28 | auto shortcutsFile = fs::File(configPath / "shortcuts.vdf", fs::File::Mode::Write);
29 | if (!shortcutsFile.isValid()) {
30 | return { };
31 | }
32 |
33 | // Parse shortcuts
34 | auto shortcuts = VDF(shortcutsFile.readBytes());
35 |
36 | // Query all app IDs from the list
37 | std::vector appIds;
38 | for (const auto &[key, value] : shortcuts["shortcuts"].set()) {
39 | if (value.set().contains("appid"))
40 | appIds.emplace_back(value.set().at("appid").integer());
41 | }
42 |
43 | return appIds;
44 | }
45 |
46 | [[nodiscard]]
47 | u64 getAppId() const noexcept {
48 | return this->m_appId;
49 | }
50 |
51 | [[nodiscard]]
52 | u32 getShortAppId() const noexcept {
53 | return this->m_appId >> 32;
54 | }
55 |
56 | [[nodiscard]]
57 | u32 getShortcutId() const noexcept {
58 | return (this->m_appId >> 32) - 0x1'0000'0000;
59 | }
60 |
61 | [[nodiscard]]
62 | explicit operator u64() const noexcept {
63 | return this->m_appId;
64 | }
65 |
66 | private:
67 | u64 m_appId;
68 | };
69 |
70 | }
--------------------------------------------------------------------------------
/lib/include/steam/api/steam_api.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 |
12 | namespace steam::api {
13 |
14 | std::optional addGameShortcut(const User &user, const std::string &appName, const std::fs::path &exePath, const std::string &launchOptions = "", const std::vector &tags = { }, bool hidden = false);
15 | bool removeGameShortcut(const User &user, const AppId &appId);
16 | bool enableProtonForApp(AppId appId, bool enabled);
17 |
18 | bool restartSteam();
19 | }
--------------------------------------------------------------------------------
/lib/include/steam/api/steam_grid_db.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 |
7 | namespace steam::api {
8 |
9 | class SteamGridDBAPI {
10 | public:
11 | SteamGridDBAPI(const std::string &apiKey) : m_apiKey(apiKey) { }
12 |
13 | const static inline std::string BaseUrl = "https://www.steamgriddb.com/api/v2";
14 |
15 | struct SearchEntry {
16 | std::vector types;
17 | AppId id;
18 | std::string name;
19 | bool verified;
20 | };
21 |
22 | struct ImageResult {
23 | AppId id;
24 | u32 score;
25 | std::string style;
26 | std::string url;
27 | std::string thumb;
28 | };
29 |
30 | std::future> search(const std::string &keyword);
31 |
32 | std::future> getGrids(const AppId &appId);
33 | std::future> getHeroes(const AppId &appId);
34 | std::future> getLogos(const AppId &appId);
35 | std::future> getIcons(const AppId &appId);
36 |
37 | private:
38 | std::string m_apiKey;
39 | Net m_net;
40 | };
41 |
42 | }
--------------------------------------------------------------------------------
/lib/include/steam/api/user.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 |
11 | namespace steam::api {
12 |
13 | class User {
14 | public:
15 | explicit User(u32 userId) : m_userId(userId), m_userName(queryUserName(userId)) { }
16 |
17 | static const std::vector& getUsers() {
18 | static auto users = []{
19 | std::vector users;
20 |
21 | for (const auto &folder : std::fs::directory_iterator(fs::getSteamDirectory() / "userdata")) {
22 | u32 userId = std::stoi(folder.path().filename());
23 | if (userId == 0)
24 | continue;
25 |
26 | users.emplace_back(userId);
27 | }
28 |
29 | std::erase_if(users, [](const User &user){
30 | return user.getName().empty();
31 | });
32 |
33 | return users;
34 | }();
35 |
36 | return users;
37 | }
38 |
39 | [[nodiscard]]
40 | u32 getId() const noexcept {
41 | return this->m_userId;
42 | }
43 |
44 | [[nodiscard]]
45 | const std::string &getName() const noexcept {
46 | return this->m_userName;
47 | }
48 |
49 | public:
50 | static std::string queryUserName(u32 userId) {
51 | auto localConfigFile = fs::File(fs::getSteamDirectory() / "userdata" / std::to_string(userId) / "config" / "localconfig.vdf", fs::File::Mode::Read);
52 | if (!localConfigFile.isValid())
53 | return { };
54 |
55 | auto localConfig = steam::KeyValues(localConfigFile.readString());
56 | if (!localConfig.contains("UserLocalConfigStore")) return { };
57 |
58 | auto userLocalConfigStore = localConfig["UserLocalConfigStore"];
59 | if (!userLocalConfigStore.contains("friends")) return { };
60 |
61 | auto friends = userLocalConfigStore["friends"];
62 | if (!friends.contains(std::to_string(userId))) return { };
63 |
64 | auto user = friends[std::to_string(userId)];
65 | if (!user.contains("name")) return { };
66 |
67 | return user["name"].string();
68 | }
69 |
70 | u32 m_userId;
71 | std::string m_userName;
72 | };
73 |
74 | }
--------------------------------------------------------------------------------
/lib/include/steam/file_formats/keyvalues.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include