├── steam_appid.txt ├── rustfmt.toml ├── steamworks-sys ├── lib │ └── steam │ │ ├── public │ │ └── steam │ │ │ ├── isteamvideo.h │ │ │ ├── isteaminventory.h │ │ │ ├── steam_gameserver.h │ │ │ ├── steamhttpenums.h │ │ │ ├── isteamgamecoordinator.h │ │ │ ├── isteamgameserverstats.h │ │ │ ├── steamencryptedappticket.h │ │ │ ├── isteamps3overlayrenderer.h │ │ │ ├── lib │ │ │ ├── win32 │ │ │ │ ├── sdkencryptedappticket.dll │ │ │ │ └── sdkencryptedappticket.lib │ │ │ ├── osx │ │ │ │ └── libsdkencryptedappticket.dylib │ │ │ ├── win64 │ │ │ │ ├── sdkencryptedappticket64.dll │ │ │ │ └── sdkencryptedappticket64.lib │ │ │ ├── linux32 │ │ │ │ └── libsdkencryptedappticket.so │ │ │ └── linux64 │ │ │ │ └── libsdkencryptedappticket.so │ │ │ ├── steamuniverse.h │ │ │ ├── isteamappticket.h │ │ │ ├── isteamparentalsettings.h │ │ │ ├── isteammusic.h │ │ │ ├── steamps3params.h │ │ │ ├── steamtypes.h │ │ │ ├── isteammusicremote.h │ │ │ ├── isteamscreenshots.h │ │ │ ├── steamnetworkingfakeip.h │ │ │ ├── isteamdualsense.h │ │ │ ├── matchmakingtypes.h │ │ │ ├── isteamclient.h │ │ │ ├── isteamapps.h │ │ │ └── steam_api_common.h │ │ └── redistributable_bin │ │ ├── steam_api.dll │ │ ├── steam_api.lib │ │ ├── win64 │ │ ├── steam_api64.dll │ │ └── steam_api64.lib │ │ ├── linux32 │ │ └── libsteam_api.so │ │ ├── linux64 │ │ └── libsteam_api.so │ │ └── osx │ │ └── libsteam_api.dylib ├── src │ └── lib.rs ├── Makefile ├── Cargo.toml └── build.rs ├── examples ├── lobby │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── game-server │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── workshop │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── networking-messages │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── chat-example │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs └── achievements │ └── Readme.md ├── .gitignore ├── updating-steamworks-sdk-version.md ├── LICENSE-MIT ├── Cargo.toml ├── .github └── workflows │ ├── ci.yml │ └── rebuild-bindings.yml ├── README.md └── src ├── user_stats ├── stat_callback.rs └── stats.rs ├── networking_sockets_callback.rs ├── timeline.rs ├── networking.rs ├── screenshots.rs ├── remote_play.rs ├── app.rs ├── callback.rs ├── networking_utils.rs └── input.rs /steam_appid.txt: -------------------------------------------------------------------------------- 1 | 480 -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | ignore = [ 2 | "steamworks-sys", 3 | ] -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamvideo.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/isteamvideo.h -------------------------------------------------------------------------------- /examples/lobby/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lobby" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | steamworks = { path = "../../" } 8 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteaminventory.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/isteaminventory.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steam_gameserver.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/steam_gameserver.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steamhttpenums.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/steamhttpenums.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/steam_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/steam_api.dll -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/steam_api.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/steam_api.lib -------------------------------------------------------------------------------- /examples/game-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "game-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | steamworks = { path = "../../" } 8 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamgamecoordinator.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/isteamgamecoordinator.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamgameserverstats.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/isteamgameserverstats.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steamencryptedappticket.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/steamencryptedappticket.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamps3overlayrenderer.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/isteamps3overlayrenderer.h -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/win64/steam_api64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/win64/steam_api64.dll -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/win64/steam_api64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/win64/steam_api64.lib -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/linux32/libsteam_api.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/linux32/libsteam_api.so -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/linux64/libsteam_api.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/linux64/libsteam_api.so -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/redistributable_bin/osx/libsteam_api.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/redistributable_bin/osx/libsteam_api.dylib -------------------------------------------------------------------------------- /examples/workshop/README.md: -------------------------------------------------------------------------------- 1 | # Workshop Example 2 | 3 | This example elaborates on how to create, upload and delete a workshop item! 4 | 5 | Please check out the commented code in `main.rs`. 6 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/win32/sdkencryptedappticket.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/win32/sdkencryptedappticket.dll -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/win32/sdkencryptedappticket.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/win32/sdkencryptedappticket.lib -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/osx/libsdkencryptedappticket.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/osx/libsdkencryptedappticket.dylib -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/win64/sdkencryptedappticket64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/win64/sdkencryptedappticket64.dll -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/win64/sdkencryptedappticket64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/win64/sdkencryptedappticket64.lib -------------------------------------------------------------------------------- /examples/networking-messages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "networking-messages" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | steamworks = { path = "../.." } 8 | macroquad = "0.4" 9 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/linux32/libsdkencryptedappticket.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/linux32/libsdkencryptedappticket.so -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/lib/linux64/libsdkencryptedappticket.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Noxime/steamworks-rs/HEAD/steamworks-sys/lib/steam/public/steam/lib/linux64/libsdkencryptedappticket.so -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea/ 5 | .vscode/ 6 | 7 | *.iml 8 | steamworks-sys/lib/steam/* 9 | !steamworks-sys/lib/steam/redistributable_bin/ 10 | !steamworks-sys/lib/steam/public/ 11 | -------------------------------------------------------------------------------- /examples/workshop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "workshop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | steamworks = { path = "../../" } -------------------------------------------------------------------------------- /examples/chat-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chat-example" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | steamworks = { path = "../../" } 10 | macroquad = "0.4" 11 | clipboard = "0.5.0" -------------------------------------------------------------------------------- /steamworks-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_snake_case)] 4 | 5 | #[cfg(target_os = "windows")] 6 | include!("windows_bindings.rs"); 7 | 8 | #[cfg(target_os = "macos")] 9 | include!("macos_bindings.rs"); 10 | 11 | #[cfg(target_os = "linux")] 12 | include!("linux_bindings.rs"); 13 | -------------------------------------------------------------------------------- /examples/chat-example/README.md: -------------------------------------------------------------------------------- 1 | # Chat Example 2 | [Macroquad](https://github.com/not-fl3/macroquad) is used for graphics. 3 | 4 | ## Usage 5 | * Create Lobby - You become a **host (server)** and LobbyId will copy in clipboard. You have to send this LobbyId to other players. 6 | * Connect - Text from input field will be parsed into LobbyId. If LobbyId is right you'll become a **client**. -------------------------------------------------------------------------------- /steamworks-sys/Makefile: -------------------------------------------------------------------------------- 1 | all: windows linux macos 2 | 3 | clean: 4 | cargo clean 5 | 6 | windows: clean 7 | cargo build --target x86_64-pc-windows-gnu --features "rebuild-bindings" 8 | 9 | linux: clean 10 | cargo build --target x86_64-unknown-linux-gnu --features "rebuild-bindings" 11 | 12 | macos: clean 13 | cargo build --target x86_64-apple-darwin --features "rebuild-bindings" 14 | 15 | local: clean 16 | cargo build --features "rebuild-bindings" -------------------------------------------------------------------------------- /examples/networking-messages/README.md: -------------------------------------------------------------------------------- 1 | # NetworkingMessages Example 2 | 3 | This example demonstrates how to use the NetworkingMessages API to send and receive messages over the network. It sends 4 | the mouse position to all friends playing the same game. Green circle is your local player and red circles are your 5 | friends. 6 | 7 | ## Note 8 | To use this example, you need to have two instances on two machines with two steam accounts running at the same time. 9 | -------------------------------------------------------------------------------- /steamworks-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "steamworks-sys" 3 | version = "0.12.0" 4 | authors = ["Thinkofname"] 5 | build = "build.rs" 6 | description = "Provides raw bindings to the steamworks sdk" 7 | license = "MIT / Apache-2.0" 8 | repository = "https://github.com/Noxime/steamworks-rs" 9 | documentation = "https://docs.rs/steamworks-sys" 10 | edition = "2021" 11 | rust-version = "1.80.0" 12 | 13 | [features] 14 | default = [] 15 | rebuild-bindings = ["bindgen"] 16 | 17 | [dependencies] 18 | 19 | [build-dependencies] 20 | bindgen = { version = "0.69.2", optional = true } 21 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steamuniverse.h: -------------------------------------------------------------------------------- 1 | //========= Copyright � 1996-2008, Valve LLC, All rights reserved. ============ 2 | // 3 | // Purpose: 4 | // 5 | //============================================================================= 6 | 7 | #ifndef STEAMUNIVERSE_H 8 | #define STEAMUNIVERSE_H 9 | #ifdef _WIN32 10 | #pragma once 11 | #endif 12 | 13 | 14 | // Steam universes. Each universe is a self-contained Steam instance. 15 | enum EUniverse 16 | { 17 | k_EUniverseInvalid = 0, 18 | k_EUniversePublic = 1, 19 | k_EUniverseBeta = 2, 20 | k_EUniverseInternal = 3, 21 | k_EUniverseDev = 4, 22 | // k_EUniverseRC = 5, // no such universe anymore 23 | k_EUniverseMax 24 | }; 25 | 26 | 27 | #endif // STEAMUNIVERSE_H 28 | -------------------------------------------------------------------------------- /updating-steamworks-sdk-version.md: -------------------------------------------------------------------------------- 1 | # How to update the Steamworks SDK version 2 | This file is so that I remember how to do it. 3 | 4 | # 1. Download the latest version 5 | https://partner.steamgames.com/downloads/steamworks_sdk.zip 6 | 7 | # 2. Extract the contents 8 | Copy the contents of "sdk/" in the archive to the steamworks-sys/lib/steam directory. 9 | 10 | # 3. Rebuild the bindings 11 | Commit the changes so far and push to a new branch. Go to the rebuild-bindings 12 | action and run it on your new branch. 13 | https://github.com/Noxime/steamworks-rs/actions/workflows/rebuild-bindings.yml 14 | 15 | You can then download the newly generated bindings from the artifacts section. 16 | Replace steamworks-sys/src/*_bindings.rs with the new bindings. 17 | 18 | # 4. Build steamworks-sys 19 | cargo build -p steamworks-sys 20 | 21 | # 5. Upgrade rest of the crate 22 | Now build the main crate and fix any issues that arise with the new version. 23 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Matthew Collins 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "steamworks" 3 | version = "0.12.2" 4 | authors = ["Thinkofname"] 5 | description = "Provides rust friendly bindings to the steamworks sdk" 6 | license = "MIT / Apache-2.0" 7 | repository = "https://github.com/Noxime/steamworks-rs" 8 | documentation = "https://docs.rs/steamworks" 9 | keywords = ["steam", "gamedev"] 10 | categories = ["games"] 11 | edition = "2021" 12 | rust-version = "1.80.0" 13 | 14 | [features] 15 | default = [] 16 | raw-bindings = [] 17 | image = ["dep:image"] 18 | serde = ["dep:serde", "bitflags/serde"] 19 | 20 | [workspace] 21 | members = [ 22 | "./steamworks-sys", 23 | "examples/chat-example", 24 | "examples/workshop", 25 | "examples/lobby", 26 | "examples/networking-messages", 27 | "examples/game-server", 28 | ] 29 | 30 | [dependencies] 31 | steamworks-sys = { path = "./steamworks-sys", version = "0.12.0" } 32 | thiserror = "2.0" 33 | bitflags = "2.9" 34 | serde = { version = "1.0", features = ["derive"], optional = true } 35 | paste = "1.0.11" 36 | image = { version = "0.25.1", optional = true, default-features = false } 37 | 38 | [dev-dependencies] 39 | serial_test = "3.2" 40 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamappticket.h: -------------------------------------------------------------------------------- 1 | //====== Copyright 1996-2008, Valve Corporation, All rights reserved. ======= 2 | // 3 | // Purpose: a private, but well versioned, interface to get at critical bits 4 | // of a steam3 appticket - consumed by the simple drm wrapper to let it 5 | // ask about ownership with greater confidence. 6 | // 7 | //============================================================================= 8 | 9 | #ifndef ISTEAMAPPTICKET_H 10 | #define ISTEAMAPPTICKET_H 11 | #pragma once 12 | 13 | //----------------------------------------------------------------------------- 14 | // Purpose: hand out a reasonable "future proof" view of an app ownership ticket 15 | // the raw (signed) buffer, and indices into that buffer where the appid and 16 | // steamid are located. the sizes of the appid and steamid are implicit in 17 | // (each version of) the interface - currently uin32 appid and uint64 steamid 18 | //----------------------------------------------------------------------------- 19 | class ISteamAppTicket 20 | { 21 | public: 22 | virtual uint32 GetAppOwnershipTicketData( uint32 nAppID, void *pvBuffer, uint32 cbBufferLength, uint32 *piAppId, uint32 *piSteamId, uint32 *piSignature, uint32 *pcbSignature ) = 0; 23 | }; 24 | 25 | #define STEAMAPPTICKET_INTERFACE_VERSION "STEAMAPPTICKET_INTERFACE_VERSION001" 26 | 27 | 28 | #endif // ISTEAMAPPTICKET_H 29 | -------------------------------------------------------------------------------- /examples/lobby/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc; 2 | 3 | use steamworks::*; 4 | 5 | fn main() { 6 | let client = Client::init().unwrap(); 7 | 8 | let matchmaking = client.matchmaking(); 9 | 10 | let (sender_create_lobby, receiver_create_lobby) = mpsc::channel(); 11 | let (sender_lobby_chat_msg, receiver_lobby_chat_msg) = mpsc::channel(); 12 | 13 | matchmaking.create_lobby(LobbyType::Private, 4, move |result| match result { 14 | Ok(lobby_id) => { 15 | sender_create_lobby.send(lobby_id).unwrap(); 16 | println!("Created lobby: [{}]", lobby_id.raw()) 17 | } 18 | Err(err) => panic!("Error: {}", err), 19 | }); 20 | 21 | client.register_callback(move |message: LobbyChatMsg| { 22 | println!("Lobby chat message received: {:?}", message); 23 | sender_lobby_chat_msg.send(message).unwrap(); 24 | }); 25 | 26 | loop { 27 | client.run_callbacks(); 28 | 29 | if let Ok(lobby_id) = receiver_create_lobby.try_recv() { 30 | println!("Sending message to lobby chat..."); 31 | matchmaking 32 | .send_lobby_chat_message(lobby_id, &[0, 1, 2, 3, 4, 5]) 33 | .expect("Failed to send chat message to lobby"); 34 | } 35 | 36 | if let Ok(message) = receiver_lobby_chat_msg.try_recv() { 37 | let mut buffer = vec![0; 256]; 38 | let buffer = matchmaking.get_lobby_chat_entry( 39 | message.lobby, 40 | message.chat_id, 41 | buffer.as_mut_slice(), 42 | ); 43 | println!("Message buffer: [{:?}]", buffer); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | os: [windows-latest, ubuntu-latest, macos-latest] 13 | toolchain: [stable, nightly, "1.80.0"] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions-rs/toolchain@v1 19 | id: toolchain 20 | with: 21 | toolchain: ${{ matrix.toolchain }} 22 | profile: minimal 23 | components: rustfmt, clippy 24 | override: true 25 | 26 | - name: Install alsa and udev 27 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libudev-dev libx11-dev libxi-dev libgl1-mesa-dev libasound2-dev libxcb-shape0-dev libxcb-xfixes0-dev 28 | if: runner.os == 'linux' 29 | 30 | - name: Setup cache 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | ~/.cargo/registry 35 | ~/.cargo/git 36 | target 37 | key: ${{ runner.os }}-test-rustc-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} 38 | 39 | - uses: actions-rs/cargo@v1 40 | if: runner.os == 'linux' 41 | with: 42 | command: fmt 43 | args: --all -- --check 44 | 45 | - uses: actions-rs/cargo@v1 46 | with: 47 | command: build 48 | args: --lib 49 | env: 50 | CARGO_INCREMENTAL: 0 51 | RUSTFLAGS: "-C debuginfo=0" 52 | 53 | - uses: actions-rs/cargo@v1 54 | if: ${{ matrix.toolchain }}== ['stable', 'nightly'] 55 | with: 56 | command: build 57 | args: --examples 58 | env: 59 | CARGO_INCREMENTAL: 0 60 | RUSTFLAGS: "-C debuginfo=0" -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamparentalsettings.h: -------------------------------------------------------------------------------- 1 | //====== Copyright � 2013-, Valve Corporation, All rights reserved. ======= 2 | // 3 | // Purpose: Interface to Steam parental settings (Family View) 4 | // 5 | //============================================================================= 6 | 7 | #ifndef ISTEAMPARENTALSETTINGS_H 8 | #define ISTEAMPARENTALSETTINGS_H 9 | #ifdef _WIN32 10 | #pragma once 11 | #endif 12 | 13 | #include "steam_api_common.h" 14 | 15 | // Feature types for parental settings 16 | enum EParentalFeature 17 | { 18 | k_EFeatureInvalid = 0, 19 | k_EFeatureStore = 1, 20 | k_EFeatureCommunity = 2, 21 | k_EFeatureProfile = 3, 22 | k_EFeatureFriends = 4, 23 | k_EFeatureNews = 5, 24 | k_EFeatureTrading = 6, 25 | k_EFeatureSettings = 7, 26 | k_EFeatureConsole = 8, 27 | k_EFeatureBrowser = 9, 28 | k_EFeatureParentalSetup = 10, 29 | k_EFeatureLibrary = 11, 30 | k_EFeatureTest = 12, 31 | k_EFeatureSiteLicense = 13, 32 | k_EFeatureKioskMode_Deprecated = 14, 33 | k_EFeatureBlockAlways = 15, 34 | k_EFeatureMax 35 | }; 36 | 37 | class ISteamParentalSettings 38 | { 39 | public: 40 | virtual bool BIsParentalLockEnabled() = 0; 41 | virtual bool BIsParentalLockLocked() = 0; 42 | 43 | virtual bool BIsAppBlocked( AppId_t nAppID ) = 0; 44 | virtual bool BIsAppInBlockList( AppId_t nAppID ) = 0; 45 | 46 | virtual bool BIsFeatureBlocked( EParentalFeature eFeature ) = 0; 47 | virtual bool BIsFeatureInBlockList( EParentalFeature eFeature ) = 0; 48 | }; 49 | 50 | #define STEAMPARENTALSETTINGS_INTERFACE_VERSION "STEAMPARENTALSETTINGS_INTERFACE_VERSION001" 51 | 52 | // Global interface accessor 53 | inline ISteamParentalSettings *SteamParentalSettings(); 54 | STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamParentalSettings *, SteamParentalSettings, STEAMPARENTALSETTINGS_INTERFACE_VERSION ); 55 | 56 | //----------------------------------------------------------------------------- 57 | // Purpose: Callback for querying UGC 58 | //----------------------------------------------------------------------------- 59 | struct SteamParentalSettingsChanged_t 60 | { 61 | enum { k_iCallback = k_ISteamParentalSettingsCallbacks + 1 }; 62 | }; 63 | 64 | 65 | #endif // ISTEAMPARENTALSETTINGS_H 66 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteammusic.h: -------------------------------------------------------------------------------- 1 | //============ Copyright (c) Valve Corporation, All rights reserved. ============ 2 | 3 | #ifndef ISTEAMMUSIC_H 4 | #define ISTEAMMUSIC_H 5 | #ifdef _WIN32 6 | #pragma once 7 | #endif 8 | 9 | #include "steam_api_common.h" 10 | 11 | //----------------------------------------------------------------------------- 12 | // Purpose: 13 | //----------------------------------------------------------------------------- 14 | enum AudioPlayback_Status 15 | { 16 | AudioPlayback_Undefined = 0, 17 | AudioPlayback_Playing = 1, 18 | AudioPlayback_Paused = 2, 19 | AudioPlayback_Idle = 3 20 | }; 21 | 22 | 23 | //----------------------------------------------------------------------------- 24 | // Purpose: Functions to control music playback in the steam client 25 | //----------------------------------------------------------------------------- 26 | class ISteamMusic 27 | { 28 | public: 29 | virtual bool BIsEnabled() = 0; 30 | virtual bool BIsPlaying() = 0; 31 | 32 | virtual AudioPlayback_Status GetPlaybackStatus() = 0; 33 | 34 | virtual void Play() = 0; 35 | virtual void Pause() = 0; 36 | virtual void PlayPrevious() = 0; 37 | virtual void PlayNext() = 0; 38 | 39 | // volume is between 0.0 and 1.0 40 | virtual void SetVolume( float flVolume ) = 0; 41 | virtual float GetVolume() = 0; 42 | 43 | }; 44 | 45 | #define STEAMMUSIC_INTERFACE_VERSION "STEAMMUSIC_INTERFACE_VERSION001" 46 | 47 | // Global interface accessor 48 | inline ISteamMusic *SteamMusic(); 49 | STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamMusic *, SteamMusic, STEAMMUSIC_INTERFACE_VERSION ); 50 | 51 | // callbacks 52 | #if defined( VALVE_CALLBACK_PACK_SMALL ) 53 | #pragma pack( push, 4 ) 54 | #elif defined( VALVE_CALLBACK_PACK_LARGE ) 55 | #pragma pack( push, 8 ) 56 | #else 57 | #error steam_api_common.h should define VALVE_CALLBACK_PACK_xxx 58 | #endif 59 | 60 | 61 | STEAM_CALLBACK_BEGIN( PlaybackStatusHasChanged_t, k_iSteamMusicCallbacks + 1 ) 62 | STEAM_CALLBACK_END(0) 63 | 64 | STEAM_CALLBACK_BEGIN( VolumeHasChanged_t, k_iSteamMusicCallbacks + 2 ) 65 | STEAM_CALLBACK_MEMBER( 0, float, m_flNewVolume ) 66 | STEAM_CALLBACK_END(1) 67 | 68 | #pragma pack( pop ) 69 | 70 | 71 | #endif // #define ISTEAMMUSIC_H 72 | -------------------------------------------------------------------------------- /.github/workflows/rebuild-bindings.yml: -------------------------------------------------------------------------------- 1 | name: Rebuild bindings 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - "steamworks-sys" 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: [windows-latest, ubuntu-latest, macos-latest] 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: actions-rs/toolchain@v1 23 | id: toolchain 24 | with: 25 | toolchain: stable 26 | profile: minimal 27 | components: rustfmt, clippy 28 | override: true 29 | 30 | - name: Install alsa and udev 31 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev 32 | if: runner.os == 'linux' 33 | 34 | - name: Setup cache 35 | uses: actions/cache@v4 36 | with: 37 | path: | 38 | ~/.cargo/registry 39 | ~/.cargo/git 40 | target 41 | key: ${{ runner.os }}-test-rustc-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} 42 | 43 | - run: cargo build --features "rebuild-bindings" 44 | working-directory: steamworks-sys 45 | env: 46 | STEAM_SDK_LOCATION: ./lib/steam 47 | 48 | - uses: actions/upload-artifact@v4 49 | with: 50 | name: artifact-linux-bindings 51 | path: steamworks-sys/src/linux_bindings.rs 52 | if: matrix.os == 'ubuntu-latest' 53 | 54 | - uses: actions/upload-artifact@v4 55 | with: 56 | name: artifact-macos-bindings 57 | path: steamworks-sys/src/macos_bindings.rs 58 | if: matrix.os == 'macos-latest' 59 | 60 | - uses: actions/upload-artifact@v4 61 | with: 62 | name: artifact-windows-bindings 63 | path: steamworks-sys/src/windows_bindings.rs 64 | if: matrix.os == 'windows-latest' 65 | 66 | push: 67 | needs: build 68 | runs-on: ubuntu-latest 69 | 70 | steps: 71 | - uses: actions/checkout@v2 72 | 73 | - name: Download generated bindings 74 | uses: actions/download-artifact@v4 75 | with: 76 | pattern: artifact-*-bindings 77 | path: steamworks-sys/src/ 78 | merge-multiple: true 79 | 80 | - name: Commit changes 81 | run: | 82 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 83 | git config --local user.name "github-actions[bot]" 84 | git add steamworks-sys/src/*_bindings.rs 85 | git commit -m "Update bindings" || echo "No changes to commit" 86 | 87 | - name: Push changes 88 | uses: ad-m/github-push-action@master 89 | with: 90 | branch: ${{ github.ref }} 91 | -------------------------------------------------------------------------------- /examples/networking-messages/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use macroquad::prelude::*; 4 | use steamworks::{ 5 | networking_types::{NetworkingIdentity, SendFlags}, 6 | FriendFlags, 7 | }; 8 | 9 | #[macroquad::main("steamworks-rs")] 10 | async fn main() { 11 | // 480 is Spacewar!, the Steamworks SDK example app. 12 | let client = 13 | steamworks::Client::init_app(480).expect("Steam is not running or has not been detected"); 14 | 15 | // Get the API interfaces 16 | let friends = client.friends(); 17 | let messages = client.networking_messages(); 18 | 19 | // Even though NetworkingMessages appears as ad-hoc API, it's internally session based. We must accept any incoming 20 | // messages before communicating with the peer. 21 | messages.session_request_callback(move |req| { 22 | println!("Accepting session request from {:?}", req.remote()); 23 | assert!(req.accept()); 24 | }); 25 | 26 | // Install a callback to debug print failed peer connections 27 | messages.session_failed_callback(|info| { 28 | eprintln!("Session failed: {info:#?}"); 29 | }); 30 | 31 | // Keep track of all players 32 | let mut peers = HashMap::new(); 33 | 34 | loop { 35 | // Poll the internal callbacks 36 | client.run_callbacks(); 37 | 38 | clear_background(BLACK); 39 | 40 | set_camera(&Camera2D::from_display_rect(Rect::new( 41 | -1.0, 1.0, 2.0, -2.0, 42 | ))); 43 | 44 | // Draw us at our mouse position 45 | let me = mouse_position_local(); 46 | draw_circle(me.x, me.y, 0.1, GREEN); 47 | 48 | // Send our mouse position to all friends 49 | for friend in friends.get_friends(FriendFlags::IMMEDIATE) { 50 | let identity = NetworkingIdentity::new_steam_id(friend.id()); 51 | 52 | // Convert our position to bytes 53 | let mut data = [0; 8]; 54 | data[0..4].copy_from_slice(&me.x.to_le_bytes()); 55 | data[4..8].copy_from_slice(&me.y.to_le_bytes()); 56 | 57 | let _ = 58 | messages.send_message_to_user(identity, SendFlags::UNRELIABLE_NO_DELAY, &data, 0); 59 | } 60 | 61 | // Receive messages from the network 62 | for message in messages.receive_messages_on_channel(0, 100) { 63 | let peer = message.identity_peer(); 64 | let data = message.data(); 65 | 66 | // Convert peer position from bytes 67 | let peer_x = 68 | f32::from_le_bytes(data[0..4].try_into().expect("Someone sent bad message")); 69 | let peer_y = 70 | f32::from_le_bytes(data[4..8].try_into().expect("Someone sent bad message")); 71 | 72 | peers.insert(peer.debug_string(), (peer_x, peer_y)); 73 | } 74 | 75 | // Draw all peers 76 | for peer in peers.values() { 77 | draw_circle(peer.0, peer.1, 0.1, RED); 78 | } 79 | 80 | next_frame().await; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # steamworks 2 | [![crates.io](https://img.shields.io/crates/v/steamworks.svg)](https://crates.io/crates/steamworks) 3 | [![Documentation](https://docs.rs/steamworks/badge.svg)](https://docs.rs/steamworks) 4 | ![License](https://img.shields.io/crates/l/steamworks.svg) 5 | 6 | This crate provides Rust bindings to the [Steamworks SDK](https://partner.steamgames.com/doc/sdk). 7 | 8 | ## Usage 9 | Add the following to your `Cargo.toml`: 10 | 11 | ```toml 12 | [dependencies] 13 | steamworks = "0.12.0" 14 | ``` 15 | 16 | | Crate | SDK | MSRV | 17 | | ------ | ----- | ------ | 18 | | git | 1.62 | 1.80.0 | 19 | | 0.12.0 | 1.62 | 1.80.0 | 20 | | 0.11.0 | 1.58a | 1.71.1 | 21 | | 0.10.0 | 1.54 | 1.56.1 | 22 | | 0.9.0 | 1.53a | 1.56.1 | 23 | 24 | ## Example 25 | You can find more examples in [examples](examples/). 26 | ```rust 27 | use steamworks::AppId; 28 | use steamworks::Client; 29 | use steamworks::FriendFlags; 30 | use steamworks::PersonaStateChange; 31 | 32 | fn main() { 33 | let client = Client::init().unwrap(); 34 | 35 | let _cb = client.register_callback(|p: PersonaStateChange| { 36 | println!("Got callback: {:?}", p); 37 | }); 38 | 39 | let utils = client.utils(); 40 | println!("Utils:"); 41 | println!("AppId: {:?}", utils.app_id()); 42 | println!("UI Language: {}", utils.ui_language()); 43 | 44 | let apps = client.apps(); 45 | println!("Apps"); 46 | println!("IsInstalled(480): {}", apps.is_app_installed(AppId(480))); 47 | println!("InstallDir(480): {}", apps.app_install_dir(AppId(480))); 48 | println!("BuildId: {}", apps.app_build_id()); 49 | println!("AppOwner: {:?}", apps.app_owner()); 50 | println!("Langs: {:?}", apps.available_game_languages()); 51 | println!("Lang: {}", apps.current_game_language()); 52 | println!("Beta: {:?}", apps.current_beta_name()); 53 | 54 | let friends = client.friends(); 55 | println!("Friends"); 56 | let list = friends.get_friends(FriendFlags::IMMEDIATE); 57 | println!("{:?}", list); 58 | for f in &list { 59 | println!("Friend: {:?} - {}({:?})", f.id(), f.name(), f.state()); 60 | friends.request_user_information(f.id(), true); 61 | } 62 | 63 | for _ in 0..50 { 64 | client.run_callbacks(); 65 | ::std::thread::sleep(::std::time::Duration::from_millis(100)); 66 | } 67 | } 68 | ``` 69 | 70 | ## Features 71 | `serde`: This feature enables serialization and deserialization of some types with `serde`. 72 | `image`: This feature allows accessing image data like icons with `image` crate. 73 | 74 | ## License 75 | This crate is dual-licensed under [Apache](./LICENSE-APACHE) and 76 | [MIT](./LICENSE-MIT), except for the files in [`steamworks-sys/lib/steam/`] 77 | 78 | ## Help, I can't run my game! 79 | If you are seeing errors like `STATUS_DLL_NOT_FOUND`, `Image not found` etc. You are likely missing the Steamworks SDK Redistributable files. Steamworks-rs loads the SDK dynamically, so the libraries need to exist somewhere the operating system can find them. This is likely next to your game binary (.exe on windows). You can find the required files in the SDK release ZIP, under `lib\steam\redistributable_bin`. See #63 for further details 80 | -------------------------------------------------------------------------------- /examples/game-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddrV4; 2 | use std::str::FromStr; 3 | use std::thread::sleep; 4 | use std::time::{Duration, Instant}; 5 | use steamworks::CallbackResult::{ 6 | GSClientApprove, GSClientDeny, GSClientGroupStatus, GSClientKick, P2PSessionRequest, 7 | SteamServersConnected, 8 | }; 9 | use steamworks::ServerMode; 10 | 11 | const BUFFER_SIZE: usize = 1500; 12 | 13 | fn main() { 14 | let bind_address = 15 | SocketAddrV4::from_str("0.0.0.0:54321").expect("Could not parse bind address"); 16 | 17 | // Client::init is not required with Server::Init as long as you set the app id via steam_appid.txt or env var 18 | unsafe { 19 | std::env::set_var("SteamAppId", "480"); 20 | } 21 | let (server, _) = steamworks::Server::init( 22 | *bind_address.ip(), // in reality, this should be the external ip of the server 23 | bind_address.port() - 1, 24 | bind_address.port(), // For some games, this port is actually the "main" port (and the game_port is unused) 25 | ServerMode::AuthenticationAndSecure, 26 | "123456", 27 | ) 28 | .expect("Could not register server with Steam Master server list"); 29 | 30 | server.set_server_name("Rusty Server"); 31 | server.set_dedicated_server(true); 32 | server.set_max_players(10); 33 | 34 | server.enable_heartbeats(true); 35 | server.log_on_anonymous(); 36 | 37 | println!("{:?}", server.steam_id()); 38 | 39 | let networking = server.networking(); 40 | let networking_messages = server.networking_messages(); 41 | 42 | networking_messages.session_request_callback(|req| { 43 | println!("Session request event"); 44 | req.accept(); 45 | }); 46 | 47 | let mut buffer = vec![0; BUFFER_SIZE]; 48 | 49 | loop { 50 | let start = Instant::now(); 51 | 52 | server.process_callbacks(|event| match event { 53 | SteamServersConnected(..) => { 54 | println!("Steam Servers Connected"); 55 | } 56 | P2PSessionRequest(request) => { 57 | println!( 58 | "Received a new Server P2P session request for {:?}", 59 | request.remote 60 | ); 61 | networking.accept_p2p_session(request.remote); 62 | } 63 | GSClientApprove(info) => { 64 | println!("GSClientApprove {:?}", info); 65 | } 66 | GSClientDeny(denial) => { 67 | println!("GSClientDeny {:?}", denial); 68 | } 69 | GSClientKick(kick) => { 70 | println!("GSClientKick {:?}", kick); 71 | } 72 | GSClientGroupStatus(status) => { 73 | println!("GSClientGroupStatus {:?}", status); 74 | } 75 | _ => { 76 | println!("Unhandled event"); 77 | } 78 | }); 79 | 80 | let size = networking.is_p2p_packet_available_on_channel(0); 81 | if (size.is_some()) { 82 | let (sender, size) = networking 83 | .read_p2p_packet_from_channel(&mut buffer, 2) 84 | .expect("Could not read P2P packet"); 85 | println!( 86 | "recv from: {:?}, size: {:?}, data: {:X?}", 87 | sender, size, &buffer 88 | ); 89 | } 90 | 91 | let difference = Duration::from_secs_f32(1f32 / 60f32).checked_sub(start.elapsed()); 92 | if (difference != None) { 93 | sleep(difference.unwrap()); 94 | } else { 95 | println!("Event loop lagging!") 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/user_stats/stat_callback.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Callback type after calling 4 | /// [`request_current_stats()`](struct.UserStats.html#method.request_current_stats). 5 | /// 6 | /// # Example 7 | /// 8 | /// ```no_run 9 | /// # use steamworks::*; 10 | /// # let client = steamworks::Client::init().unwrap(); 11 | /// let callback_handle = client.register_callback(|val: UserStatsReceived| { 12 | /// if val.result.is_err() { 13 | /// // ... 14 | /// } 15 | /// }); 16 | /// ``` 17 | #[derive(Clone, Debug)] 18 | pub struct UserStatsReceived { 19 | pub steam_id: SteamId, 20 | pub game_id: GameId, 21 | pub result: Result<(), SteamError>, 22 | } 23 | 24 | impl_callback!(cb: UserStatsReceived_t => UserStatsReceived { 25 | Self { 26 | steam_id: SteamId(cb.m_steamIDUser.m_steamid.m_unAll64Bits), 27 | game_id: GameId(cb.m_nGameID), 28 | result: crate::to_steam_result(cb.m_eResult), 29 | } 30 | }); 31 | 32 | /// Callback triggered by [`store()`](stats/struct.StatsHelper.html#method.store). 33 | /// 34 | /// # Example 35 | /// 36 | /// ```no_run 37 | /// # use steamworks::*; 38 | /// # let client = steamworks::Client::init().unwrap(); 39 | /// let callback_handle = client.register_callback(|val: UserStatsStored| { 40 | /// if val.result.is_err() { 41 | /// // ... 42 | /// } 43 | /// }); 44 | /// ``` 45 | #[derive(Clone, Debug)] 46 | pub struct UserStatsStored { 47 | pub game_id: GameId, 48 | pub result: Result<(), SteamError>, 49 | } 50 | 51 | impl_callback!(cb: UserStatsStored_t => UserStatsStored { 52 | Self { 53 | game_id: GameId(cb.m_nGameID), 54 | result: crate::to_steam_result(cb.m_eResult), 55 | } 56 | }); 57 | 58 | /// Result of a request to store the achievements on the server, or an "indicate progress" call. 59 | /// If both `current_progress` and `max_progress` are zero, that means the achievement has been 60 | /// fully unlocked. 61 | /// 62 | /// # Example 63 | /// 64 | /// ```no_run 65 | /// # use steamworks::*; 66 | /// # let client = steamworks::Client::init().unwrap(); 67 | /// let callback_handle = client.register_callback(|val: UserAchievementStored| { 68 | /// // ... 69 | /// }); 70 | /// ``` 71 | #[derive(Clone, Debug)] 72 | pub struct UserAchievementStored { 73 | pub game_id: GameId, 74 | pub achievement_name: String, 75 | /// Current progress towards the achievement. 76 | pub current_progress: u32, 77 | /// The total amount of progress required to unlock. 78 | pub max_progress: u32, 79 | } 80 | 81 | impl_callback!(cb: UserAchievementStored_t => UserAchievementStored { 82 | let name = CStr::from_ptr(cb.m_rgchAchievementName.as_ptr()).to_owned(); 83 | Self { 84 | game_id: GameId(cb.m_nGameID), 85 | achievement_name: name.into_string().unwrap(), 86 | current_progress: cb.m_nCurProgress, 87 | max_progress: cb.m_nMaxProgress, 88 | } 89 | }); 90 | 91 | /// Result of a request to retrieve the achievement icon if the icon was not available at the time of the function call. 92 | /// # Example 93 | /// 94 | /// ```no_run 95 | /// # use steamworks::*; 96 | /// # let client = steamworks::Client::init().unwrap(); 97 | /// let callback_handle = client.register_callback(|val: UserAchievementIconFetched| { 98 | /// // ... 99 | /// }); 100 | /// ``` 101 | #[derive(Clone, Debug)] 102 | pub struct UserAchievementIconFetched { 103 | pub game_id: GameId, 104 | pub achievement_name: String, 105 | pub achieved: bool, 106 | pub icon_handle: i32, 107 | } 108 | 109 | impl_callback!(cb: UserAchievementIconFetched_t => UserAchievementIconFetched { 110 | let name = CStr::from_ptr(cb.m_rgchAchievementName.as_ptr()).to_owned(); 111 | Self { 112 | game_id: GameId(cb.m_nGameID.__bindgen_anon_1.m_ulGameID), 113 | achievement_name: name.into_string().unwrap(), 114 | achieved: cb.m_bAchieved, 115 | icon_handle: cb.m_nIconHandle, 116 | } 117 | }); 118 | -------------------------------------------------------------------------------- /src/networking_sockets_callback.rs: -------------------------------------------------------------------------------- 1 | use crate::networking_sockets::NetConnection; 2 | use crate::networking_types::{ 3 | AppNetConnectionEnd, NetConnectionEnd, NetConnectionStatusChanged, NetworkingConnectionState, 4 | }; 5 | use crate::{register_callback, CallbackHandle, Inner}; 6 | use std::sync::{Arc, Weak}; 7 | use steamworks_sys as sys; 8 | use sys::ISteamNetworkingSockets; 9 | 10 | /// All independent connections (to a remote host) and listening sockets share the same Callback for 11 | /// `NetConnectionStatusChangedCallback`. This function either returns the existing handle, or creates a new 12 | /// handler. 13 | pub(crate) fn get_or_create_connection_callback( 14 | inner: Arc, 15 | sockets: *mut ISteamNetworkingSockets, 16 | ) -> Arc { 17 | let mut network_socket_data = inner.networking_sockets_data.lock().unwrap(); 18 | if let Some(callback) = network_socket_data.connection_callback.upgrade() { 19 | callback 20 | } else { 21 | let handler = ConnectionCallbackHandler { 22 | inner: Arc::downgrade(&inner), 23 | sockets, 24 | }; 25 | let callback = unsafe { 26 | register_callback(&inner, move |event: NetConnectionStatusChanged| { 27 | handler.callback(event); 28 | }) 29 | }; 30 | 31 | let callback = Arc::new(callback); 32 | network_socket_data.connection_callback = Arc::downgrade(&callback); 33 | callback 34 | } 35 | } 36 | 37 | pub(crate) struct ConnectionCallbackHandler { 38 | inner: Weak, 39 | sockets: *mut ISteamNetworkingSockets, 40 | } 41 | 42 | unsafe impl Send for ConnectionCallbackHandler {} 43 | unsafe impl Sync for ConnectionCallbackHandler {} 44 | 45 | impl ConnectionCallbackHandler { 46 | pub(crate) fn callback(&self, event: NetConnectionStatusChanged) { 47 | if let Some(socket) = event.connection_info.listen_socket() { 48 | self.listen_socket_callback(socket, event); 49 | } else { 50 | self.independent_connection_callback(event); 51 | } 52 | } 53 | 54 | fn listen_socket_callback( 55 | &self, 56 | socket_handle: sys::HSteamListenSocket, 57 | event: NetConnectionStatusChanged, 58 | ) { 59 | if let Some(inner) = self.inner.upgrade() { 60 | let data = inner.networking_sockets_data.lock().unwrap(); 61 | if let Some((socket, sender)) = data 62 | .sockets 63 | .get(&socket_handle) 64 | .and_then(|(socket, sender)| socket.upgrade().map(|socket| (socket, sender))) 65 | { 66 | let connection_handle = event.connection; 67 | let state = event.connection_info.state().expect("invalid state"); 68 | if let Ok(event) = event.into_listen_socket_event(socket) { 69 | if let Err(_err) = sender.send(event) { 70 | // If the main socket was dropped, but the inner socket still exists, reject all new connections, 71 | // as there's no way to accept them. 72 | if let NetworkingConnectionState::Connecting = state { 73 | self.reject_connection(connection_handle); 74 | } 75 | } 76 | } else { 77 | // Ignore events that couldn't be converted 78 | } 79 | } 80 | } 81 | } 82 | 83 | fn reject_connection(&self, connection_handle: sys::HSteamNetConnection) { 84 | if let Some(inner) = self.inner.upgrade() { 85 | NetConnection::new_internal(connection_handle, self.sockets, inner.clone()).close( 86 | NetConnectionEnd::App(AppNetConnectionEnd::generic_normal()).into(), 87 | Some("no new connections will be accepted"), 88 | false, 89 | ); 90 | } 91 | } 92 | 93 | fn independent_connection_callback(&self, _event: NetConnectionStatusChanged) { 94 | // TODO: Handle event for independent connections 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steamps3params.h: -------------------------------------------------------------------------------- 1 | //====== Copyright 1996-2008, Valve Corporation, All rights reserved. ======= 2 | // 3 | // Purpose: 4 | // 5 | //============================================================================= 6 | 7 | #ifndef STEAMPS3PARAMS_H 8 | #define STEAMPS3PARAMS_H 9 | #ifdef _WIN32 10 | #pragma once 11 | #endif 12 | 13 | //----------------------------------------------------------------------------------------------------------------------------------------------------------// 14 | // PlayStation 3 initialization parameters 15 | // 16 | // The following structure must be passed to when loading steam_api_ps3.prx 17 | //----------------------------------------------------------------------------------------------------------------------------------------------------------// 18 | #define STEAM_PS3_PATH_MAX 1055 19 | #define STEAM_PS3_SERVICE_ID_MAX 32 20 | #define STEAM_PS3_COMMUNICATION_ID_MAX 10 21 | #define STEAM_PS3_COMMUNICATION_SIG_MAX 160 22 | #define STEAM_PS3_LANGUAGE_MAX 64 23 | #define STEAM_PS3_REGION_CODE_MAX 16 24 | #define STEAM_PS3_CURRENT_PARAMS_VER 2 25 | struct SteamPS3Params_t 26 | { 27 | uint32 m_unVersion; // set to STEAM_PS3_CURRENT_PARAMS_VER 28 | 29 | void *pReserved; 30 | uint32 m_nAppId; // set to your game's appid 31 | 32 | char m_rgchInstallationPath[ STEAM_PS3_PATH_MAX ]; // directory containing latest steam prx's and sdata. Can be read only (BDVD) 33 | char m_rgchSystemCache[ STEAM_PS3_PATH_MAX ]; // temp working cache, not persistent 34 | char m_rgchGameData[ STEAM_PS3_PATH_MAX ]; // persistent game data path for storing user data 35 | char m_rgchNpServiceID[ STEAM_PS3_SERVICE_ID_MAX ]; 36 | char m_rgchNpCommunicationID[ STEAM_PS3_COMMUNICATION_ID_MAX ]; 37 | char m_rgchNpCommunicationSig[ STEAM_PS3_COMMUNICATION_SIG_MAX ]; 38 | 39 | // Language should be one of the following. must be zero terminated 40 | // danish 41 | // dutch 42 | // english 43 | // finnish 44 | // french 45 | // german 46 | // italian 47 | // korean 48 | // norwegian 49 | // polish 50 | // portuguese 51 | // russian 52 | // schinese 53 | // spanish 54 | // swedish 55 | // tchinese 56 | char m_rgchSteamLanguage[ STEAM_PS3_LANGUAGE_MAX ]; 57 | 58 | // region codes are "SCEA", "SCEE", "SCEJ". must be zero terminated 59 | char m_rgchRegionCode[ STEAM_PS3_REGION_CODE_MAX ]; 60 | 61 | // Should be SYS_TTYP3 through SYS_TTYP10, if it's 0 then Steam won't spawn a 62 | // thread to read console input at all. Using this let's you use Steam console commands 63 | // like: profile_on, profile_off, profile_dump, mem_stats, mem_validate. 64 | unsigned int m_cSteamInputTTY; 65 | 66 | struct Ps3netInit_t 67 | { 68 | bool m_bNeedInit; 69 | void *m_pMemory; 70 | int m_nMemorySize; 71 | int m_flags; 72 | } m_sysNetInitInfo; 73 | 74 | struct Ps3jpgInit_t 75 | { 76 | bool m_bNeedInit; 77 | } m_sysJpgInitInfo; 78 | 79 | struct Ps3pngInit_t 80 | { 81 | bool m_bNeedInit; 82 | } m_sysPngInitInfo; 83 | 84 | struct Ps3sysutilUserInfo_t 85 | { 86 | bool m_bNeedInit; 87 | } m_sysSysUtilUserInfo; 88 | 89 | bool m_bIncludeNewsPage; 90 | }; 91 | 92 | 93 | //----------------------------------------------------------------------------------------------------------------------------------------------------------// 94 | // PlayStation 3 memory structure 95 | //----------------------------------------------------------------------------------------------------------------------------------------------------------// 96 | #define STEAMPS3_MALLOC_INUSE 0x53D04A51 97 | #define STEAMPS3_MALLOC_SYSTEM 0x0D102C48 98 | #define STEAMPS3_MALLOC_OK 0xFFD04A51 99 | struct SteamPS3Memory_t 100 | { 101 | bool m_bSingleAllocation; // If true, Steam will request one 6MB allocation and use the returned memory for all future allocations 102 | // If false, Steam will make call malloc for each allocation 103 | 104 | // required function pointers 105 | void* (*m_pfMalloc)(size_t); 106 | void* (*m_pfRealloc)(void *, size_t); 107 | void (*m_pfFree)(void *); 108 | size_t (*m_pUsable_size)(void*); 109 | }; 110 | 111 | 112 | #endif // STEAMPS3PARAMS_H 113 | -------------------------------------------------------------------------------- /steamworks-sys/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "rebuild-bindings")] 2 | extern crate bindgen; 3 | 4 | fn main() -> Result<(), Box> { 5 | use std::env; 6 | use std::fs::{self}; 7 | use std::path::{Path, PathBuf}; 8 | 9 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 10 | let sdk_loc = if let Ok(sdk_loc) = env::var("STEAM_SDK_LOCATION") { 11 | Path::new(&sdk_loc).to_path_buf() 12 | } else { 13 | let mut path = PathBuf::new(); 14 | path.push(env::var("CARGO_MANIFEST_DIR").unwrap()); 15 | path.push("lib"); 16 | path.push("steam"); 17 | path 18 | }; 19 | println!("cargo:rerun-if-env-changed=STEAM_SDK_LOCATION"); 20 | 21 | let triple = env::var("TARGET").unwrap(); 22 | let mut lib = "steam_api"; 23 | let mut link_path = sdk_loc.join("redistributable_bin"); 24 | if triple.contains("windows") { 25 | if !triple.contains("i686") { 26 | lib = "steam_api64"; 27 | link_path.push("win64"); 28 | } 29 | } else if triple.contains("linux") { 30 | if triple.contains("i686") { 31 | link_path.push("linux32"); 32 | } else { 33 | link_path.push("linux64"); 34 | } 35 | } else if triple.contains("darwin") { 36 | link_path.push("osx"); 37 | } else { 38 | panic!("Unsupported OS"); 39 | }; 40 | 41 | if triple.contains("windows") { 42 | let dll_file = format!("{}.dll", lib); 43 | let lib_file = format!("{}.lib", lib); 44 | fs::copy(link_path.join(&dll_file), out_path.join(dll_file))?; 45 | fs::copy(link_path.join(&lib_file), out_path.join(lib_file))?; 46 | } else if triple.contains("darwin") { 47 | fs::copy( 48 | link_path.join("libsteam_api.dylib"), 49 | out_path.join("libsteam_api.dylib"), 50 | )?; 51 | } else if triple.contains("linux") { 52 | fs::copy( 53 | link_path.join("libsteam_api.so"), 54 | out_path.join("libsteam_api.so"), 55 | )?; 56 | } 57 | 58 | println!("cargo:rustc-link-search={}", out_path.display()); 59 | println!("cargo:rustc-link-lib=dylib={}", lib); 60 | 61 | #[cfg(feature = "rebuild-bindings")] 62 | { 63 | let target_os = if triple.contains("windows") { 64 | "windows" 65 | } else if triple.contains("darwin") { 66 | "macos" 67 | } else if triple.contains("linux") { 68 | "linux" 69 | } else { 70 | panic!("Unsupported OS"); 71 | }; 72 | let binding_path = Path::new(&format!("src/{}_bindings.rs", target_os)).to_owned(); 73 | let bindings = bindgen::Builder::default() 74 | .header( 75 | sdk_loc 76 | .join("public/steam/steam_api_flat.h") 77 | .to_string_lossy(), 78 | ) 79 | .header( 80 | sdk_loc 81 | .join("public/steam/steam_gameserver.h") 82 | .to_string_lossy(), 83 | ) 84 | .clang_arg("-xc++") 85 | .clang_arg("-std=c++11") 86 | .clang_arg(format!("-I{}", sdk_loc.join("public").display())) 87 | .allowlist_function("Steam.*") 88 | .allowlist_var(".*") // TODO: Prune constants 89 | .allowlist_type(".*") // TODO: Prune types 90 | .default_enum_style(bindgen::EnumVariation::Rust { 91 | non_exhaustive: true, 92 | }) 93 | .bitfield_enum("EMarketNotAllowedReasonFlags") 94 | .bitfield_enum("EBetaBranchFlags") 95 | .bitfield_enum("EFriendFlags") 96 | .bitfield_enum("EPersonaChange") 97 | .bitfield_enum("ERemoteStoragePlatform") 98 | .bitfield_enum("EChatSteamIDInstanceFlags") 99 | .bitfield_enum("ESteamItemFlags") 100 | .bitfield_enum("EOverlayToStoreFlag") 101 | .bitfield_enum("EChatSteamIDInstanceFlags") 102 | .generate() 103 | .expect("Unable to generate bindings"); 104 | 105 | bindings 106 | .write_to_file(binding_path) 107 | .expect("Couldn't write bindings!"); 108 | } 109 | 110 | Ok(()) 111 | } 112 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steamtypes.h: -------------------------------------------------------------------------------- 1 | //========= Copyright 1996-2022, Valve LLC, All rights reserved. ============ 2 | 3 | #ifndef STEAMTYPES_H 4 | #define STEAMTYPES_H 5 | 6 | #define S_CALLTYPE __cdecl 7 | // WARNING: __cdecl is potentially #defined away in steam_api_common.h 8 | 9 | // Steam-specific types. Defined here so this header file can be included in other code bases. 10 | #ifndef WCHARTYPES_H 11 | typedef unsigned char uint8; 12 | #endif 13 | 14 | #ifdef __GNUC__ 15 | #if __GNUC__ < 4 16 | #error "Steamworks requires GCC 4.X (4.2 or 4.4 have been tested)" 17 | #endif 18 | #endif 19 | 20 | #if defined(__LP64__) || defined(__x86_64__) || defined(_WIN64) || defined(__aarch64__) || defined(__s390x__) 21 | #define X64BITS 22 | #endif 23 | 24 | #if !defined(VALVE_BIG_ENDIAN) 25 | #if defined( __GNUC__ ) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 26 | #define VALVE_BIG_ENDIAN 1 27 | #endif 28 | #endif 29 | 30 | typedef unsigned char uint8; 31 | typedef signed char int8; 32 | 33 | #if defined( _WIN32 ) && !defined( __GNUC__ ) 34 | 35 | typedef __int16 int16; 36 | typedef unsigned __int16 uint16; 37 | typedef __int32 int32; 38 | typedef unsigned __int32 uint32; 39 | typedef __int64 int64; 40 | typedef unsigned __int64 uint64; 41 | 42 | typedef int64 lint64; 43 | typedef uint64 ulint64; 44 | 45 | #ifdef X64BITS 46 | typedef __int64 intp; // intp is an integer that can accomodate a pointer 47 | typedef unsigned __int64 uintp; // (ie, sizeof(intp) >= sizeof(int) && sizeof(intp) >= sizeof(void *) 48 | #else 49 | typedef __int32 intp; 50 | typedef unsigned __int32 uintp; 51 | #endif 52 | 53 | #else // _WIN32 54 | 55 | typedef short int16; 56 | typedef unsigned short uint16; 57 | typedef int int32; 58 | typedef unsigned int uint32; 59 | typedef long long int64; 60 | typedef unsigned long long uint64; 61 | 62 | // [u]int64 are actually defined as 'long long' and gcc 64-bit 63 | // doesn't automatically consider them the same as 'long int'. 64 | // Changing the types for [u]int64 is complicated by 65 | // there being many definitions, so we just 66 | // define a 'long int' here and use it in places that would 67 | // otherwise confuse the compiler. 68 | typedef long int lint64; 69 | typedef unsigned long int ulint64; 70 | 71 | #ifdef X64BITS 72 | typedef long long intp; 73 | typedef unsigned long long uintp; 74 | #else 75 | typedef int intp; 76 | typedef unsigned int uintp; 77 | #endif 78 | 79 | #endif // else _WIN32 80 | 81 | typedef uint32 AppId_t; 82 | const AppId_t k_uAppIdInvalid = 0x0; 83 | 84 | // AppIds and DepotIDs also presently share the same namespace 85 | typedef uint32 DepotId_t; 86 | const DepotId_t k_uDepotIdInvalid = 0x0; 87 | 88 | // RTime32. Seconds elapsed since Jan 1 1970, i.e. unix timestamp. 89 | // It's the same as time_t, but it is always 32-bit and unsigned. 90 | typedef uint32 RTime32; 91 | 92 | // handle to a Steam API call 93 | typedef uint64 SteamAPICall_t; 94 | const SteamAPICall_t k_uAPICallInvalid = 0x0; 95 | 96 | typedef uint32 AccountID_t; 97 | const AccountID_t k_uAccountIdInvalid = 0; 98 | 99 | // Party Beacon ID 100 | typedef uint64 PartyBeaconID_t; 101 | const PartyBeaconID_t k_ulPartyBeaconIdInvalid = 0; 102 | 103 | enum ESteamIPType 104 | { 105 | k_ESteamIPTypeIPv4 = 0, 106 | k_ESteamIPTypeIPv6 = 1, 107 | }; 108 | 109 | #pragma pack( push, 1 ) 110 | 111 | struct SteamIPAddress_t 112 | { 113 | union { 114 | 115 | uint32 m_unIPv4; // Host order 116 | uint8 m_rgubIPv6[16]; // Network order! Same as inaddr_in6. (0011:2233:4455:6677:8899:aabb:ccdd:eeff) 117 | 118 | // Internal use only 119 | uint64 m_ipv6Qword[2]; // big endian 120 | }; 121 | 122 | ESteamIPType m_eType; 123 | 124 | bool IsSet() const 125 | { 126 | if ( k_ESteamIPTypeIPv4 == m_eType ) 127 | { 128 | return m_unIPv4 != 0; 129 | } 130 | else 131 | { 132 | return m_ipv6Qword[0] !=0 || m_ipv6Qword[1] != 0; 133 | } 134 | } 135 | 136 | static SteamIPAddress_t IPv4Any() 137 | { 138 | SteamIPAddress_t ipOut; 139 | ipOut.m_eType = k_ESteamIPTypeIPv4; 140 | ipOut.m_unIPv4 = 0; 141 | 142 | return ipOut; 143 | } 144 | 145 | static SteamIPAddress_t IPv6Any() 146 | { 147 | SteamIPAddress_t ipOut; 148 | ipOut.m_eType = k_ESteamIPTypeIPv6; 149 | ipOut.m_ipv6Qword[0] = 0; 150 | ipOut.m_ipv6Qword[1] = 0; 151 | 152 | return ipOut; 153 | } 154 | 155 | static SteamIPAddress_t IPv4Loopback() 156 | { 157 | SteamIPAddress_t ipOut; 158 | ipOut.m_eType = k_ESteamIPTypeIPv4; 159 | ipOut.m_unIPv4 = 0x7f000001; 160 | 161 | return ipOut; 162 | } 163 | 164 | static SteamIPAddress_t IPv6Loopback() 165 | { 166 | SteamIPAddress_t ipOut; 167 | ipOut.m_eType = k_ESteamIPTypeIPv6; 168 | ipOut.m_ipv6Qword[0] = 0; 169 | ipOut.m_ipv6Qword[1] = 0; 170 | ipOut.m_rgubIPv6[15] = 1; 171 | 172 | return ipOut; 173 | } 174 | }; 175 | 176 | #pragma pack( pop ) 177 | 178 | #endif // STEAMTYPES_H 179 | -------------------------------------------------------------------------------- /src/timeline.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::time::Duration; 3 | 4 | pub struct Timeline { 5 | pub(crate) timeline: *mut sys::ISteamTimeline, 6 | /// Whether the client's steam API is not recent enough. 7 | pub(crate) disabled: bool, 8 | pub(crate) _inner: Arc, 9 | } 10 | 11 | pub enum TimelineGameMode { 12 | /// The player is fully loaded into the game and playing. 13 | Playing, 14 | /// The player is in a multiplayer lobby. 15 | Staging, 16 | /// The player is in the game's main menu or a pause menu. 17 | Menus, 18 | /// The player is waiting for a loading screen. 19 | LoadingScreen, 20 | } 21 | 22 | impl From for sys::ETimelineGameMode { 23 | fn from(mode: TimelineGameMode) -> Self { 24 | match mode { 25 | TimelineGameMode::Playing => sys::ETimelineGameMode::k_ETimelineGameMode_Playing, 26 | TimelineGameMode::Staging => sys::ETimelineGameMode::k_ETimelineGameMode_Staging, 27 | TimelineGameMode::Menus => sys::ETimelineGameMode::k_ETimelineGameMode_Menus, 28 | TimelineGameMode::LoadingScreen => { 29 | sys::ETimelineGameMode::k_ETimelineGameMode_LoadingScreen 30 | } 31 | } 32 | } 33 | } 34 | 35 | pub enum TimelineEventClipPriority { 36 | /// This event is not appropriate as a clip. 37 | None, 38 | /// The user may want to make a clip around this event. 39 | Standard, 40 | /// The player will be likely to want a clip around event, 41 | /// and those clips should be promoted more prominently than clips with the [TimelineEventClipPriority::Standard] priority. 42 | Featured, 43 | } 44 | 45 | impl From for sys::ETimelineEventClipPriority { 46 | fn from(priority: TimelineEventClipPriority) -> Self { 47 | match priority { 48 | TimelineEventClipPriority::None => { 49 | sys::ETimelineEventClipPriority::k_ETimelineEventClipPriority_None 50 | } 51 | TimelineEventClipPriority::Standard => { 52 | sys::ETimelineEventClipPriority::k_ETimelineEventClipPriority_Standard 53 | } 54 | TimelineEventClipPriority::Featured => { 55 | sys::ETimelineEventClipPriority::k_ETimelineEventClipPriority_Featured 56 | } 57 | } 58 | } 59 | } 60 | 61 | impl Timeline { 62 | /// Changes the color of the timeline bar. 63 | pub fn set_timeline_game_mode(&self, mode: TimelineGameMode) { 64 | if self.disabled { 65 | return; 66 | } 67 | 68 | unsafe { 69 | sys::SteamAPI_ISteamTimeline_SetTimelineGameMode(self.timeline, mode.into()); 70 | } 71 | } 72 | 73 | /// Sets a description for the current game state in the timeline. 74 | /// These help the user to find specific moments in the timeline when saving clips. 75 | /// Setting a new state description replaces any previous description. 76 | pub fn set_timeline_state_description(&self, description: &str, duration: Duration) { 77 | if self.disabled { 78 | return; 79 | } 80 | 81 | let description = CString::new(description).unwrap(); 82 | 83 | unsafe { 84 | sys::SteamAPI_ISteamTimeline_SetTimelineTooltip( 85 | self.timeline, 86 | description.as_ptr(), 87 | duration.as_secs_f32(), 88 | ) 89 | } 90 | } 91 | 92 | /// Clears the previous set game state in the timeline. 93 | pub fn clear_timeline_state_description(&self, duration: Duration) { 94 | if self.disabled { 95 | return; 96 | } 97 | 98 | unsafe { 99 | sys::SteamAPI_ISteamTimeline_ClearTimelineTooltip(self.timeline, duration.as_secs_f32()) 100 | } 101 | } 102 | 103 | /// Use this to mark an event on the Timeline. 104 | /// The event can be instantaneous or take some amount of time to complete, 105 | /// depending on the value passed in `duration`. 106 | pub fn add_timeline_event( 107 | &self, 108 | icon: &str, 109 | title: &str, 110 | description: &str, 111 | priority: u32, 112 | start_offset_seconds: f32, 113 | duration: Duration, 114 | clip_priority: TimelineEventClipPriority, 115 | ) { 116 | if self.disabled { 117 | return; 118 | } 119 | 120 | let icon = CString::new(icon).unwrap(); 121 | let title = CString::new(title).unwrap(); 122 | let description = CString::new(description).unwrap(); 123 | let duration = duration.as_secs_f32(); 124 | 125 | unsafe { 126 | let _handle = sys::SteamAPI_ISteamTimeline_AddRangeTimelineEvent( 127 | self.timeline, 128 | icon.as_ptr(), 129 | title.as_ptr(), 130 | description.as_ptr(), 131 | priority, 132 | start_offset_seconds, 133 | duration, 134 | clip_priority.into(), 135 | ); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /examples/achievements/Readme.md: -------------------------------------------------------------------------------- 1 | # 🏆 Achievements Example 2 | *By [Jackson0ne](https://github.com/Jackson0ne)* 3 | 4 | This example outlines how to use the following achievement functions from the Steamworks API within `steamworks-rs`: 5 | 6 | `get_achievement_achieved_percent()` 7 | - 8 | 9 | > *⚠ This function **requires** a successful callback to be received from `request_global_achievement_percentages(...)` before it will return any data!* 10 | 11 | Returns the percentage of users who have unlocked the specified achievement. 12 | 13 | To use, you'll first need to call `client.user_stats().request_global_achievement_percentages(...)`, and obtain the result from within the returned callback. 14 | 15 | #### Example: 16 | 17 | ```rust 18 | use steamworks::{Client,AppId}; 19 | 20 | fn main() { 21 | let (client,single) = Client::init_app(AppId(4000)).unwrap(); 22 | let name = "GMA_BALLEATER"; 23 | 24 | client.user_stats().request_global_achievement_percentages(move|result| { 25 | if !result.is_err() { 26 | let user_stats = client.user_stats(); 27 | let achievement = user_stats.achievement(name); 28 | 29 | let ach_percent = achievement.get_achievement_achieved_percent().unwrap(); 30 | } else { 31 | eprintln!("Error fetching achievement percentage for {}",name); 32 | } 33 | }); 34 | 35 | for _ in 0..50 { 36 | single.run_callbacks(); 37 | std::thread::sleep(std::time::Duration::from_millis(100)); 38 | } 39 | } 40 | ``` 41 | 42 | `get_achievement_display_attribute("name" | "desc" | "hidden")` 43 | - 44 | 45 | Returns a string for the result of the specified attribute type. Accepted values are: 46 | 47 | - `"name"`: The friendly (*non-API*) name of the achievement 48 | - `"desc"`: The achievement description 49 | - `"hidden"`: Whether the achievement is hidden (`"1"`) or not (`"0"`). 50 | 51 | > *As the returned value is always a string, the `"hidden"` value will need to be parsed for comparison!* 52 | 53 | #### Example: 54 | 55 | ```rust 56 | use steamworks::{Client,AppId}; 57 | 58 | fn main() { 59 | let (client,single) = Client::init_app(AppId(4000)).unwrap(); 60 | let name = "GMA_BALLEATER"; 61 | 62 | let user_stats = client.user_stats(); 63 | let achievement = user_stats.achievement(name); 64 | 65 | let ach_name = achievement.get_achievement_display_attribute("name").unwrap(); 66 | let ach_desc = achievement.get_achievement_display_attribute("desc").unwrap(); 67 | let ach_hidden = achievement.get_achievement_display_attribute("hidden").unwrap().parse::().unwrap(); 68 | 69 | println!( 70 | "Name: {:?}\nDesc: {:?}\nHidden?: {:?}", 71 | ach_name, 72 | ach_desc, 73 | ach_hidden != 0 74 | ); 75 | } 76 | ``` 77 | 78 | `get_achievement_icon()` 79 | - 80 | 81 | Returns a `Vec` buffer containing the image data for the specified achievement. 82 | 83 | 84 | - The icon is always returned as `64px` x `64px`. 85 | - The version of the icon that is downloaded - i.e. locked (*grey*) vs unlocked (*colour*) - is dependent on whether the achievement has been unlocked or not at the time the function is called. 86 | 87 | > *As far as I can tell, there's no parameter to request a specific version!* 88 | 89 | To convert the buffer into an image, you can use an external crate to convert the `Vec` (`Uint8Array`) into a file (such as `.jpg`) and save it to disk - there's plenty of [Rust crates](https://crates.io/crates/image) or [NPM libraries](https://www.npmjs.com/package/jpeg-js) that can do this. 90 | 91 | #### Example: 92 | 93 | ```rust 94 | use steamworks::{Client,AppId}; 95 | 96 | fn main() { 97 | let (client,single) = Client::init_app(AppId(4000)).unwrap(); 98 | let name = "GMA_BALLEATER"; 99 | 100 | let user_stats = client.user_stats(); 101 | let achievement = user_stats.achievement(name); 102 | 103 | let _ach_icon_handle = achievement.get_achievement_icon().expect("Failed getting achievement icon RGBA buffer"); 104 | } 105 | ``` 106 | 107 | `get_num_achievements()` 108 | - 109 | 110 | Returns the number of achievements for the current AppId. 111 | 112 | > *Returns `0` if the current AppId has no achievements.* 113 | 114 | #### Example: 115 | 116 | ```rust 117 | use steamworks::{Client,AppId}; 118 | 119 | fn main() { 120 | let (client,single) = Client::init_app(AppId(4000)).unwrap(); 121 | 122 | let num = client.user_stats().get_num_achievements().expect("Failed to get number of achievements"); 123 | 124 | println!("{}",num); 125 | } 126 | ``` 127 | 128 | `get_achievement_names()` 129 | - 130 | 131 | Returns a `Vec` containing the API names of all achievements for the current AppId. 132 | 133 | > *The returned string value will be empty if the specified index is invalid.* 134 | 135 | #### Example: 136 | 137 | ```rust 138 | use steamworks::{Client,AppId}; 139 | 140 | fn main() { 141 | let (client,single) = Client::init_app(AppId(4000)).unwrap(); 142 | let name = "GMA_BALLEATER"; 143 | 144 | let names = client.user_stats().get_achievement_names().expect("Failed to get achievement names"); 145 | } 146 | ``` -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteammusicremote.h: -------------------------------------------------------------------------------- 1 | //============ Copyright (c) Valve Corporation, All rights reserved. ============ 2 | 3 | #ifndef ISTEAMMUSICREMOTE_H 4 | #define ISTEAMMUSICREMOTE_H 5 | #ifdef _WIN32 6 | #pragma once 7 | #endif 8 | 9 | #include "steam_api_common.h" 10 | #include "isteammusic.h" 11 | 12 | #define k_SteamMusicNameMaxLength 255 13 | #define k_SteamMusicPNGMaxLength 65535 14 | 15 | 16 | class ISteamMusicRemote 17 | { 18 | public: 19 | // Service Definition 20 | virtual bool RegisterSteamMusicRemote( const char *pchName ) = 0; 21 | virtual bool DeregisterSteamMusicRemote() = 0; 22 | virtual bool BIsCurrentMusicRemote() = 0; 23 | virtual bool BActivationSuccess( bool bValue ) = 0; 24 | 25 | virtual bool SetDisplayName( const char *pchDisplayName ) = 0; 26 | virtual bool SetPNGIcon_64x64( void *pvBuffer, uint32 cbBufferLength ) = 0; 27 | 28 | // Abilities for the user interface 29 | virtual bool EnablePlayPrevious(bool bValue) = 0; 30 | virtual bool EnablePlayNext( bool bValue ) = 0; 31 | virtual bool EnableShuffled( bool bValue ) = 0; 32 | virtual bool EnableLooped( bool bValue ) = 0; 33 | virtual bool EnableQueue( bool bValue ) = 0; 34 | virtual bool EnablePlaylists( bool bValue ) = 0; 35 | 36 | // Status 37 | virtual bool UpdatePlaybackStatus( AudioPlayback_Status nStatus ) = 0; 38 | virtual bool UpdateShuffled( bool bValue ) = 0; 39 | virtual bool UpdateLooped( bool bValue ) = 0; 40 | virtual bool UpdateVolume( float flValue ) = 0; // volume is between 0.0 and 1.0 41 | 42 | // Current Entry 43 | virtual bool CurrentEntryWillChange() = 0; 44 | virtual bool CurrentEntryIsAvailable( bool bAvailable ) = 0; 45 | virtual bool UpdateCurrentEntryText( const char *pchText ) = 0; 46 | virtual bool UpdateCurrentEntryElapsedSeconds( int nValue ) = 0; 47 | virtual bool UpdateCurrentEntryCoverArt( void *pvBuffer, uint32 cbBufferLength ) = 0; 48 | virtual bool CurrentEntryDidChange() = 0; 49 | 50 | // Queue 51 | virtual bool QueueWillChange() = 0; 52 | virtual bool ResetQueueEntries() = 0; 53 | virtual bool SetQueueEntry( int nID, int nPosition, const char *pchEntryText ) = 0; 54 | virtual bool SetCurrentQueueEntry( int nID ) = 0; 55 | virtual bool QueueDidChange() = 0; 56 | 57 | // Playlist 58 | virtual bool PlaylistWillChange() = 0; 59 | virtual bool ResetPlaylistEntries() = 0; 60 | virtual bool SetPlaylistEntry( int nID, int nPosition, const char *pchEntryText ) = 0; 61 | virtual bool SetCurrentPlaylistEntry( int nID ) = 0; 62 | virtual bool PlaylistDidChange() = 0; 63 | }; 64 | 65 | #define STEAMMUSICREMOTE_INTERFACE_VERSION "STEAMMUSICREMOTE_INTERFACE_VERSION001" 66 | 67 | // Global interface accessor 68 | inline ISteamMusicRemote *SteamMusicRemote(); 69 | STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamMusicRemote *, SteamMusicRemote, STEAMMUSICREMOTE_INTERFACE_VERSION ); 70 | 71 | // callbacks 72 | #if defined( VALVE_CALLBACK_PACK_SMALL ) 73 | #pragma pack( push, 4 ) 74 | #elif defined( VALVE_CALLBACK_PACK_LARGE ) 75 | #pragma pack( push, 8 ) 76 | #else 77 | #error steam_api_common.h should define VALVE_CALLBACK_PACK_xxx 78 | #endif 79 | 80 | 81 | STEAM_CALLBACK_BEGIN( MusicPlayerRemoteWillActivate_t, k_iSteamMusicRemoteCallbacks + 1) 82 | STEAM_CALLBACK_END(0) 83 | 84 | STEAM_CALLBACK_BEGIN( MusicPlayerRemoteWillDeactivate_t, k_iSteamMusicRemoteCallbacks + 2 ) 85 | STEAM_CALLBACK_END(0) 86 | 87 | STEAM_CALLBACK_BEGIN( MusicPlayerRemoteToFront_t, k_iSteamMusicRemoteCallbacks + 3 ) 88 | STEAM_CALLBACK_END(0) 89 | 90 | STEAM_CALLBACK_BEGIN( MusicPlayerWillQuit_t, k_iSteamMusicRemoteCallbacks + 4 ) 91 | STEAM_CALLBACK_END(0) 92 | 93 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsPlay_t, k_iSteamMusicRemoteCallbacks + 5 ) 94 | STEAM_CALLBACK_END(0) 95 | 96 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsPause_t, k_iSteamMusicRemoteCallbacks + 6 ) 97 | STEAM_CALLBACK_END(0) 98 | 99 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsPlayPrevious_t, k_iSteamMusicRemoteCallbacks + 7 ) 100 | STEAM_CALLBACK_END(0) 101 | 102 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsPlayNext_t, k_iSteamMusicRemoteCallbacks + 8 ) 103 | STEAM_CALLBACK_END(0) 104 | 105 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsShuffled_t, k_iSteamMusicRemoteCallbacks + 9 ) 106 | STEAM_CALLBACK_MEMBER( 0, bool, m_bShuffled ) 107 | STEAM_CALLBACK_END(1) 108 | 109 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsLooped_t, k_iSteamMusicRemoteCallbacks + 10 ) 110 | STEAM_CALLBACK_MEMBER(0, bool, m_bLooped ) 111 | STEAM_CALLBACK_END(1) 112 | 113 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsVolume_t, k_iSteamMusicCallbacks + 11 ) 114 | STEAM_CALLBACK_MEMBER(0, float, m_flNewVolume) 115 | STEAM_CALLBACK_END(1) 116 | 117 | STEAM_CALLBACK_BEGIN( MusicPlayerSelectsQueueEntry_t, k_iSteamMusicCallbacks + 12 ) 118 | STEAM_CALLBACK_MEMBER(0, int, nID ) 119 | STEAM_CALLBACK_END(1) 120 | 121 | STEAM_CALLBACK_BEGIN( MusicPlayerSelectsPlaylistEntry_t, k_iSteamMusicCallbacks + 13 ) 122 | STEAM_CALLBACK_MEMBER(0, int, nID ) 123 | STEAM_CALLBACK_END(1) 124 | 125 | STEAM_CALLBACK_BEGIN( MusicPlayerWantsPlayingRepeatStatus_t, k_iSteamMusicRemoteCallbacks + 14 ) 126 | STEAM_CALLBACK_MEMBER(0, int, m_nPlayingRepeatStatus ) 127 | STEAM_CALLBACK_END(1) 128 | 129 | #pragma pack( pop ) 130 | 131 | 132 | 133 | #endif // #define ISTEAMMUSICREMOTE_H 134 | -------------------------------------------------------------------------------- /src/networking.rs: -------------------------------------------------------------------------------- 1 | //! An older networking solution that is now deprecated. 2 | //! 3 | //! In the future you should use [`networking_sockets`][../networking_sockets], but for now the wrapper for the new API 4 | //! is still unfinished. 5 | 6 | use super::*; 7 | 8 | /// Access to the steam networking interface 9 | pub struct Networking { 10 | pub(crate) net: *mut sys::ISteamNetworking, 11 | pub(crate) _inner: Arc, 12 | } 13 | 14 | /// The method used to send a packet 15 | #[derive(Clone, Debug)] 16 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 17 | pub enum SendType { 18 | /// Send the packet directly over udp. 19 | /// 20 | /// Can't be larger than 1200 bytes 21 | Unreliable, 22 | /// Like `Unreliable` but doesn't buffer packets 23 | /// sent before the connection has started. 24 | UnreliableNoDelay, 25 | /// Reliable packet sending. 26 | /// 27 | /// Can't be larger than 1 megabyte. 28 | Reliable, 29 | /// Like `Reliable` but applies the nagle 30 | /// algorithm to packets being sent 31 | ReliableWithBuffering, 32 | } 33 | 34 | impl Networking { 35 | /// Accepts incoming packets from the given user 36 | /// 37 | /// Should only be called in response to a `P2PSessionRequest`. 38 | pub fn accept_p2p_session(&self, user: SteamId) -> bool { 39 | unsafe { sys::SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(self.net, user.0) } 40 | } 41 | 42 | /// Closes the p2p connection between the given user 43 | pub fn close_p2p_session(&self, user: SteamId) -> bool { 44 | unsafe { sys::SteamAPI_ISteamNetworking_CloseP2PSessionWithUser(self.net, user.0) } 45 | } 46 | 47 | /// Sends a packet to the user, starting the 48 | /// connection if it isn't started already 49 | pub fn send_p2p_packet(&self, remote: SteamId, send_type: SendType, data: &[u8]) -> bool { 50 | self.send_p2p_packet_on_channel(remote, send_type, data, 0) 51 | } 52 | 53 | /// Sends a packet to the user on a specific channel 54 | pub fn send_p2p_packet_on_channel( 55 | &self, 56 | remote: SteamId, 57 | send_type: SendType, 58 | data: &[u8], 59 | channel: i32, 60 | ) -> bool { 61 | unsafe { 62 | let send_type = match send_type { 63 | SendType::Unreliable => sys::EP2PSend::k_EP2PSendUnreliable, 64 | SendType::UnreliableNoDelay => sys::EP2PSend::k_EP2PSendUnreliableNoDelay, 65 | SendType::Reliable => sys::EP2PSend::k_EP2PSendReliable, 66 | SendType::ReliableWithBuffering => sys::EP2PSend::k_EP2PSendReliableWithBuffering, 67 | }; 68 | sys::SteamAPI_ISteamNetworking_SendP2PPacket( 69 | self.net, 70 | remote.0, 71 | data.as_ptr().cast(), 72 | data.len() as u32, 73 | send_type, 74 | channel, 75 | ) 76 | } 77 | } 78 | 79 | /// Returns whether there is a packet queued that can be read. 80 | /// 81 | /// Returns the size of the queued packet if any. 82 | pub fn is_p2p_packet_available(&self) -> Option { 83 | self.is_p2p_packet_available_on_channel(0) 84 | } 85 | 86 | /// Returns whether there is a packet available on a specific channel 87 | pub fn is_p2p_packet_available_on_channel(&self, channel: i32) -> Option { 88 | unsafe { 89 | let mut size = 0; 90 | if sys::SteamAPI_ISteamNetworking_IsP2PPacketAvailable(self.net, &mut size, channel) { 91 | Some(size as usize) 92 | } else { 93 | None 94 | } 95 | } 96 | } 97 | 98 | /// Attempts to read a queued packet into the buffer 99 | /// if there are any. 100 | /// 101 | /// Returns the steam id of the sender and the size of the 102 | /// packet. 103 | pub fn read_p2p_packet(&self, buf: &mut [u8]) -> Option<(SteamId, usize)> { 104 | self.read_p2p_packet_from_channel(buf, 0) 105 | } 106 | 107 | /// Attempts to read a queued packet into the buffer 108 | /// from a specific channel 109 | pub fn read_p2p_packet_from_channel( 110 | &self, 111 | buf: &mut [u8], 112 | channel: i32, 113 | ) -> Option<(SteamId, usize)> { 114 | unsafe { 115 | let mut size = 0; 116 | let mut remote = 0; 117 | if sys::SteamAPI_ISteamNetworking_ReadP2PPacket( 118 | self.net, 119 | buf.as_mut_ptr().cast(), 120 | buf.len() as _, 121 | &mut size, 122 | &mut remote as *mut _ as *mut _, 123 | channel, 124 | ) { 125 | Some((SteamId(remote), size as usize)) 126 | } else { 127 | None 128 | } 129 | } 130 | } 131 | } 132 | 133 | /// Called when a user wants to communicate via p2p 134 | #[derive(Clone, Debug)] 135 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 136 | pub struct P2PSessionRequest { 137 | /// The steam ID of the user requesting a p2p 138 | /// session 139 | pub remote: SteamId, 140 | } 141 | 142 | impl_callback!(cb: P2PSessionRequest_t => P2PSessionRequest { 143 | Self { 144 | remote: SteamId(cb.m_steamIDRemote.m_steamid.m_unAll64Bits), 145 | } 146 | }); 147 | 148 | #[derive(Clone, Debug)] 149 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 150 | pub struct P2PSessionConnectFail { 151 | pub remote: SteamId, 152 | pub error: u8, 153 | } 154 | 155 | impl_callback!(cb: P2PSessionConnectFail_t => P2PSessionConnectFail { 156 | Self { 157 | remote: SteamId(cb.m_steamIDRemote.m_steamid.m_unAll64Bits), 158 | error: cb.m_eP2PSessionError, 159 | } 160 | }); 161 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamscreenshots.h: -------------------------------------------------------------------------------- 1 | //====== Copyright � 1996-2008, Valve Corporation, All rights reserved. ======= 2 | // 3 | // Purpose: public interface to user remote file storage in Steam 4 | // 5 | //============================================================================= 6 | 7 | #ifndef ISTEAMSCREENSHOTS_H 8 | #define ISTEAMSCREENSHOTS_H 9 | #ifdef _WIN32 10 | #pragma once 11 | #endif 12 | 13 | #include "steam_api_common.h" 14 | 15 | const uint32 k_nScreenshotMaxTaggedUsers = 32; 16 | const uint32 k_nScreenshotMaxTaggedPublishedFiles = 32; 17 | const int k_cubUFSTagTypeMax = 255; 18 | const int k_cubUFSTagValueMax = 255; 19 | 20 | // Required with of a thumbnail provided to AddScreenshotToLibrary. If you do not provide a thumbnail 21 | // one will be generated. 22 | const int k_ScreenshotThumbWidth = 200; 23 | 24 | // Handle is valid for the lifetime of your process and no longer 25 | typedef uint32 ScreenshotHandle; 26 | #define INVALID_SCREENSHOT_HANDLE 0 27 | 28 | enum EVRScreenshotType 29 | { 30 | k_EVRScreenshotType_None = 0, 31 | k_EVRScreenshotType_Mono = 1, 32 | k_EVRScreenshotType_Stereo = 2, 33 | k_EVRScreenshotType_MonoCubemap = 3, 34 | k_EVRScreenshotType_MonoPanorama = 4, 35 | k_EVRScreenshotType_StereoPanorama = 5 36 | }; 37 | 38 | //----------------------------------------------------------------------------- 39 | // Purpose: Functions for adding screenshots to the user's screenshot library 40 | //----------------------------------------------------------------------------- 41 | class ISteamScreenshots 42 | { 43 | public: 44 | // Writes a screenshot to the user's screenshot library given the raw image data, which must be in RGB format. 45 | // The return value is a handle that is valid for the duration of the game process and can be used to apply tags. 46 | virtual ScreenshotHandle WriteScreenshot( void *pubRGB, uint32 cubRGB, int nWidth, int nHeight ) = 0; 47 | 48 | // Adds a screenshot to the user's screenshot library from disk. If a thumbnail is provided, it must be 200 pixels wide and the same aspect ratio 49 | // as the screenshot, otherwise a thumbnail will be generated if the user uploads the screenshot. The screenshots must be in either JPEG or TGA format. 50 | // The return value is a handle that is valid for the duration of the game process and can be used to apply tags. 51 | // JPEG, TGA, and PNG formats are supported. 52 | virtual ScreenshotHandle AddScreenshotToLibrary( const char *pchFilename, const char *pchThumbnailFilename, int nWidth, int nHeight ) = 0; 53 | 54 | // Causes the Steam overlay to take a screenshot. If screenshots are being hooked by the game then a ScreenshotRequested_t callback is sent back to the game instead. 55 | virtual void TriggerScreenshot() = 0; 56 | 57 | // Toggles whether the overlay handles screenshots when the user presses the screenshot hotkey, or the game handles them. If the game is hooking screenshots, 58 | // then the ScreenshotRequested_t callback will be sent if the user presses the hotkey, and the game is expected to call WriteScreenshot or AddScreenshotToLibrary 59 | // in response. 60 | virtual void HookScreenshots( bool bHook ) = 0; 61 | 62 | // Sets metadata about a screenshot's location (for example, the name of the map) 63 | virtual bool SetLocation( ScreenshotHandle hScreenshot, const char *pchLocation ) = 0; 64 | 65 | // Tags a user as being visible in the screenshot 66 | virtual bool TagUser( ScreenshotHandle hScreenshot, CSteamID steamID ) = 0; 67 | 68 | // Tags a published file as being visible in the screenshot 69 | virtual bool TagPublishedFile( ScreenshotHandle hScreenshot, PublishedFileId_t unPublishedFileID ) = 0; 70 | 71 | // Returns true if the app has hooked the screenshot 72 | virtual bool IsScreenshotsHooked() = 0; 73 | 74 | // Adds a VR screenshot to the user's screenshot library from disk in the supported type. 75 | // pchFilename should be the normal 2D image used in the library view 76 | // pchVRFilename should contain the image that matches the correct type 77 | // The return value is a handle that is valid for the duration of the game process and can be used to apply tags. 78 | // JPEG, TGA, and PNG formats are supported. 79 | virtual ScreenshotHandle AddVRScreenshotToLibrary( EVRScreenshotType eType, const char *pchFilename, const char *pchVRFilename ) = 0; 80 | }; 81 | 82 | #define STEAMSCREENSHOTS_INTERFACE_VERSION "STEAMSCREENSHOTS_INTERFACE_VERSION003" 83 | 84 | // Global interface accessor 85 | inline ISteamScreenshots *SteamScreenshots(); 86 | STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamScreenshots *, SteamScreenshots, STEAMSCREENSHOTS_INTERFACE_VERSION ); 87 | 88 | // callbacks 89 | #if defined( VALVE_CALLBACK_PACK_SMALL ) 90 | #pragma pack( push, 4 ) 91 | #elif defined( VALVE_CALLBACK_PACK_LARGE ) 92 | #pragma pack( push, 8 ) 93 | #else 94 | #error steam_api_common.h should define VALVE_CALLBACK_PACK_xxx 95 | #endif 96 | //----------------------------------------------------------------------------- 97 | // Purpose: Screenshot successfully written or otherwise added to the library 98 | // and can now be tagged 99 | //----------------------------------------------------------------------------- 100 | struct ScreenshotReady_t 101 | { 102 | enum { k_iCallback = k_iSteamScreenshotsCallbacks + 1 }; 103 | ScreenshotHandle m_hLocal; 104 | EResult m_eResult; 105 | }; 106 | 107 | //----------------------------------------------------------------------------- 108 | // Purpose: Screenshot has been requested by the user. Only sent if 109 | // HookScreenshots() has been called, in which case Steam will not take 110 | // the screenshot itself. 111 | //----------------------------------------------------------------------------- 112 | struct ScreenshotRequested_t 113 | { 114 | enum { k_iCallback = k_iSteamScreenshotsCallbacks + 2 }; 115 | }; 116 | 117 | #pragma pack( pop ) 118 | 119 | #endif // ISTEAMSCREENSHOTS_H 120 | 121 | -------------------------------------------------------------------------------- /src/screenshots.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | pub use sys::ScreenshotHandle; 4 | 5 | use super::*; 6 | 7 | /// Access to the steam screenshots interface 8 | pub struct Screenshots { 9 | pub(crate) screenshots: *mut sys::ISteamScreenshots, 10 | pub(crate) _inner: Arc, 11 | } 12 | 13 | impl Screenshots { 14 | /// Toggles whether the overlay handles screenshots when the user presses the screenshot hotkey, or if the game handles them. 15 | /// 16 | /// Hooking is disabled by default, and only ever enabled if you do so with this function. 17 | /// 18 | /// If the hooking is enabled, then the [`ScreenshotRequested`] callback will be sent if the user presses the hotkey or when [`Self::trigger_screenshot`] is called, 19 | /// and then the game is expected to call `WriteScreenshot` or [`Self::add_screenshot_to_library`] in response. 20 | /// 21 | /// You can check if hooking is enabled with [`Self::is_screenshots_hooked`]. 22 | pub fn hook_screenshots(&self, hook: bool) { 23 | unsafe { 24 | sys::SteamAPI_ISteamScreenshots_HookScreenshots(self.screenshots, hook); 25 | } 26 | } 27 | 28 | /// Checks if the app is hooking screenshots, or if the Steam Overlay is handling them. 29 | /// 30 | /// This can be toggled with [`Self::hook_screenshots`]. 31 | /// 32 | /// Returns 33 | /// - `true` if the game is hooking screenshots and is expected to handle them; otherwise, `false`. 34 | pub fn is_screenshots_hooked(&self) -> bool { 35 | unsafe { sys::SteamAPI_ISteamScreenshots_IsScreenshotsHooked(self.screenshots) } 36 | } 37 | 38 | /// Either causes the Steam Overlay to take a screenshot, or tells your screenshot manager that a screenshot needs to be taken. 39 | /// Depending on the value of [`Self::is_screenshots_hooked`]. 40 | /// 41 | /// - Triggers a [`ScreenshotRequested`] callback. 42 | /// - Triggers a [`ScreenshotReady`] callback. 43 | /// - Only causes [`ScreenshotRequested`] if hooking has been enabled with [`Self::hook_screenshots`]. 44 | /// - Otherwise [`ScreenshotReady`] will be called when the screenshot has been saved and added to the library. 45 | pub fn trigger_screenshot(&self) { 46 | unsafe { 47 | sys::SteamAPI_ISteamScreenshots_TriggerScreenshot(self.screenshots); 48 | } 49 | } 50 | 51 | /// Adds a screenshot to the user's Steam screenshot library from disk. 52 | /// 53 | /// Triggers a [`ScreenshotReady`] callback. 54 | /// The handle to this screenshot that is valid for the duration of the game process and can be used to apply tags. 55 | /// 56 | /// This call is asynchronous, a [`ScreenshotReady`] callback will be sent when the screenshot has finished writing to disk. 57 | pub fn add_screenshot_to_library( 58 | &self, 59 | filename: &Path, 60 | thumbnail_filename: Option<&Path>, 61 | width: i32, 62 | height: i32, 63 | ) -> Result { 64 | let filename = 65 | path_to_absolute_cstring(filename).ok_or(ScreenshotLibraryAddError::InvalidPath)?; 66 | 67 | let thumbnail_filename = if let Some(thumbnail_filename) = thumbnail_filename { 68 | Some( 69 | path_to_absolute_cstring(thumbnail_filename) 70 | .ok_or(ScreenshotLibraryAddError::InvalidPath)?, 71 | ) 72 | } else { 73 | None 74 | }; 75 | 76 | unsafe { 77 | let handle = sys::SteamAPI_ISteamScreenshots_AddScreenshotToLibrary( 78 | self.screenshots, 79 | filename.as_ptr(), 80 | thumbnail_filename.map_or(std::ptr::null(), |s| s.as_ptr()), 81 | width, 82 | height, 83 | ); 84 | 85 | if handle != sys::INVALID_SCREENSHOT_HANDLE { 86 | Ok(handle) 87 | } else { 88 | Err(ScreenshotLibraryAddError::SavingFailed) 89 | } 90 | } 91 | } 92 | } 93 | 94 | #[derive(Debug, Error)] 95 | pub enum ScreenshotLibraryAddError { 96 | /// Steam failed to save the file for an unspecified reason. 97 | #[error("The screenshot file could not be saved")] 98 | SavingFailed, 99 | /// One of the paths provided was invalid. 100 | #[error("Invalid path")] 101 | InvalidPath, 102 | } 103 | 104 | /// A screenshot has been requested by the user from the Steam screenshot hotkey. 105 | /// This will only be called if [`Screenshots::hook_screenshots`] has been enabled, in which case Steam will not take the screenshot itself. 106 | #[derive(Clone, Debug)] 107 | pub struct ScreenshotRequested; 108 | 109 | impl_callback!(_cb: ScreenshotRequested_t => ScreenshotRequested { 110 | Self 111 | }); 112 | 113 | #[derive(Clone, Debug, Error)] 114 | pub enum ScreenshotReadyError { 115 | /// The screenshot could not be loaded or parsed. 116 | #[error("The screenshot could not be loaded or parsed")] 117 | Fail, 118 | /// The screenshot could not be saved to the disk. 119 | #[error("The screenshot could not be saved to the disk")] 120 | IoFailure, 121 | } 122 | 123 | /// A screenshot successfully written or otherwise added to the library and can now be tagged. 124 | #[derive(Clone, Debug)] 125 | pub struct ScreenshotReady { 126 | /// The screenshot handle that has been written. 127 | pub local_handle: Result, 128 | } 129 | 130 | impl_callback!(cb: ScreenshotReady_t => ScreenshotReady { 131 | let local_handle = match cb.m_eResult { 132 | sys::EResult::k_EResultOK => Ok(cb.m_hLocal), 133 | sys::EResult::k_EResultIOFailure => Err(ScreenshotReadyError::Fail), 134 | _ => Err(ScreenshotReadyError::Fail), 135 | }; 136 | 137 | Self { local_handle } 138 | }); 139 | 140 | fn path_to_absolute_cstring(filename: &Path) -> Option { 141 | let filename = filename.canonicalize().ok()?; 142 | Some(CString::new(filename.to_str()?).unwrap()) 143 | } 144 | -------------------------------------------------------------------------------- /src/remote_play.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub struct RemotePlay { 4 | pub(crate) rp: *mut sys::ISteamRemotePlay, 5 | pub(crate) inner: Arc, 6 | } 7 | 8 | impl Clone for RemotePlay { 9 | fn clone(&self) -> Self { 10 | RemotePlay { 11 | inner: self.inner.clone(), 12 | rp: self.rp, 13 | } 14 | } 15 | } 16 | 17 | impl RemotePlay { 18 | /// Return a list of all active Remote Play sessions 19 | pub fn sessions(&self) -> Vec { 20 | unsafe { 21 | let count = sys::SteamAPI_ISteamRemotePlay_GetSessionCount(self.rp); 22 | let mut sessions = Vec::with_capacity(count as usize); 23 | 24 | for i in 0..count { 25 | let id = sys::SteamAPI_ISteamRemotePlay_GetSessionID(self.rp, i as i32); 26 | 27 | // Session might be invalid if it ended after GetSessionCount 28 | if id == 0 { 29 | continue; 30 | } 31 | 32 | sessions.push(self.session(RemotePlaySessionId::from_raw(id))) 33 | } 34 | 35 | sessions 36 | } 37 | } 38 | 39 | /// Get a remote play session from a session ID. The session may or may not be valid or active 40 | pub fn session(&self, session: RemotePlaySessionId) -> RemotePlaySession { 41 | RemotePlaySession { 42 | session, 43 | rp: self.rp, 44 | _inner: self.inner.clone(), 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 50 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 51 | pub struct RemotePlaySessionId(pub(crate) u32); 52 | 53 | impl RemotePlaySessionId { 54 | /// Creates a `RemotePlaySessionId` from a raw 32 bit value. 55 | /// 56 | /// May be useful for deserializing session ids from 57 | /// a network or save format. 58 | pub fn from_raw(id: u32) -> RemotePlaySessionId { 59 | RemotePlaySessionId(id) 60 | } 61 | 62 | /// Returns the raw 32 bit value of the lobby id 63 | /// 64 | /// May be useful for serializing session ids over a 65 | /// network or to a save format. 66 | pub fn raw(&self) -> u32 { 67 | self.0 68 | } 69 | } 70 | 71 | pub struct RemotePlaySession { 72 | session: RemotePlaySessionId, 73 | pub(crate) rp: *mut sys::ISteamRemotePlay, 74 | pub(crate) _inner: Arc, 75 | } 76 | 77 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 78 | pub enum SteamDeviceFormFactor { 79 | Phone, 80 | Tablet, 81 | Computer, 82 | TV, 83 | } 84 | 85 | impl RemotePlaySession { 86 | /// Get the user associated with this Remote Play session. This is either the logged in user or a friend when Remote 87 | /// Playing Together. 88 | pub fn user(&self) -> SteamId { 89 | unsafe { 90 | SteamId(sys::SteamAPI_ISteamRemotePlay_GetSessionSteamID( 91 | self.rp, 92 | self.session.raw(), 93 | )) 94 | } 95 | } 96 | 97 | /// Gets the client device name for this session. Returns `None` if the session has expired 98 | pub fn client_name(&self) -> Option { 99 | unsafe { 100 | let name = 101 | sys::SteamAPI_ISteamRemotePlay_GetSessionClientName(self.rp, self.session.raw()); 102 | 103 | if name.is_null() { 104 | return None; 105 | } 106 | 107 | let name = CStr::from_ptr(name); 108 | Some(name.to_string_lossy().into_owned()) 109 | } 110 | } 111 | 112 | /// Gets the client device form factor for this session. Returns `None` if the session has expired or if the form 113 | /// factor is unknown 114 | pub fn client_form_factor(&self) -> Option { 115 | unsafe { 116 | use SteamDeviceFormFactor::*; 117 | match sys::SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor( 118 | self.rp, 119 | self.session.raw(), 120 | ) { 121 | sys::ESteamDeviceFormFactor::k_ESteamDeviceFormFactorPhone => Some(Phone), 122 | sys::ESteamDeviceFormFactor::k_ESteamDeviceFormFactorTablet => Some(Tablet), 123 | sys::ESteamDeviceFormFactor::k_ESteamDeviceFormFactorComputer => Some(Computer), 124 | sys::ESteamDeviceFormFactor::k_ESteamDeviceFormFactorTV => Some(TV), 125 | _ => None, 126 | } 127 | } 128 | } 129 | 130 | /// Gets the client device resolution for this session. Returns `None` if the session has expired 131 | pub fn client_resolution(&self) -> Option<(u32, u32)> { 132 | unsafe { 133 | let mut width = 0; 134 | let mut height = 0; 135 | 136 | sys::SteamAPI_ISteamRemotePlay_BGetSessionClientResolution( 137 | self.rp, 138 | self.session.raw(), 139 | &mut width, 140 | &mut height, 141 | ) 142 | .then_some((width as u32, height as u32)) 143 | } 144 | } 145 | 146 | /// Invites a friend to join the game using Remote Play Together 147 | pub fn invite(&self, friend: SteamId) -> bool { 148 | unsafe { 149 | sys::SteamAPI_ISteamRemotePlay_BSendRemotePlayTogetherInvite(self.rp, friend.raw()) 150 | } 151 | } 152 | } 153 | 154 | #[derive(Clone, Debug)] 155 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 156 | /// A remote play session was established 157 | pub struct RemotePlayConnected { 158 | /// The remote play session ID we just connected to 159 | pub session: RemotePlaySessionId, 160 | } 161 | 162 | impl_callback!(cb: SteamRemotePlaySessionConnected_t => RemotePlayConnected { 163 | Self { 164 | session: RemotePlaySessionId::from_raw(cb.m_unSessionID), 165 | } 166 | }); 167 | 168 | #[derive(Clone, Debug)] 169 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 170 | /// A remote play session was closed 171 | pub struct RemotePlayDisconnected { 172 | /// The remote play session ID that just disconnected 173 | pub session: RemotePlaySessionId, 174 | } 175 | 176 | impl_callback!(cb: SteamRemotePlaySessionDisconnected_t => RemotePlayDisconnected { 177 | Self { 178 | session: RemotePlaySessionId::from_raw(cb.m_unSessionID), 179 | } 180 | }); 181 | -------------------------------------------------------------------------------- /examples/workshop/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{path::Path, sync::mpsc::TryRecvError}; 2 | 3 | use steamworks::{Client, PublishedFileId, UGC}; 4 | 5 | fn create_item(ugc: &UGC) { 6 | // creating a new workshop item 7 | // make sure you change the appid to the specified game 8 | ugc.create_item( 9 | steamworks::AppId(480), 10 | steamworks::FileType::Community, 11 | |create_result| { 12 | // handle the result 13 | match create_result { 14 | Ok((published_id, needs_to_agree_to_terms)) => { 15 | // if the user needs to agree to the terms of use, they will need to do that before you can upload any files 16 | // in any case, make sure you save the published_id somewhere, like a manifest file. 17 | // it is needed for all further calls 18 | if needs_to_agree_to_terms { 19 | println!( 20 | "You need to agree to the terms of use before you can upload any files" 21 | ); 22 | } else { 23 | println!("Published item with id {:?}", published_id); 24 | } 25 | } 26 | Err(e) => { 27 | // an error occurred, usually because the app is not authorized to create items 28 | // or the user is banned from the community 29 | println!("Error creating workshop item: {:?}", e); 30 | } 31 | } 32 | }, 33 | ); 34 | } 35 | 36 | fn upload_item_content(ugc: &UGC, published_id: PublishedFileId) { 37 | // uploading the content of the workshop item 38 | // this process uses a builder pattern to set properties of the item 39 | // mandatory properties are: 40 | // - title 41 | // - description 42 | // - preview_path 43 | // - content_path 44 | // - visibility 45 | // after setting the properties, call .submit() to start uploading the item 46 | // this function is unique in that it returns a handle to the upload, which can be used to 47 | // monitor the progress of the upload and needs a closure to be called when the upload is done 48 | // in this example, the watch handle is ignored for simplicity 49 | // 50 | // notes: 51 | // - once an upload is started, it cannot be cancelled! 52 | // - content_path is the path to a folder which houses the content you wish to upload 53 | let _upload_handle = ugc 54 | .start_item_update(steamworks::AppId(480), published_id) 55 | .content_path(Path::new("/absolute/path/to/content")) 56 | .preview_path(Path::new("/absolute/path/to/preview.png")) 57 | .title("Item title") 58 | .description("Item description") 59 | .tags(Vec::::new(), false) 60 | .visibility(steamworks::PublishedFileVisibility::Public) 61 | .submit(Some("My changenotes"), |upload_result| { 62 | // handle the result 63 | match upload_result { 64 | Ok((published_id, needs_to_agree_to_terms)) => { 65 | if needs_to_agree_to_terms { 66 | // as stated in the create_item function, if the user needs to agree to the terms of use, 67 | // the upload did NOT succeed, despite the result being Ok 68 | println!( 69 | "You need to agree to the terms of use before you can upload any files" 70 | ); 71 | } else { 72 | // this is the definite indicator that an item was uploaded successfully 73 | // the watch handle is NOT an accurate indicator whether the upload is done 74 | // the progress on the other hand IS accurate and can simply be used to monitor the upload 75 | println!("Uploaded item with id {:?}", published_id); 76 | } 77 | } 78 | Err(e) => { 79 | // the upload failed 80 | // the exact reason can be found in the error type 81 | println!("Error uploading item: {:?}", e); 82 | } 83 | } 84 | }); 85 | } 86 | 87 | fn delete_item(ugc: &UGC, published_id: PublishedFileId) { 88 | // deleting an item 89 | ugc.delete_item(published_id, move |delete_result| { 90 | match delete_result { 91 | Ok(()) => { 92 | // item has been deleted 93 | println!("Deleted item with id {:?}", published_id); 94 | } 95 | Err(e) => { 96 | // the item could not be deleted 97 | // usually because it is not owned by the user or it doesn't exist in the first place 98 | println!("Error deleting item: {:?}", e); 99 | } 100 | } 101 | }) 102 | } 103 | 104 | fn main() { 105 | // create a client pair 106 | let client = Client::init().expect("Steam is not running or has not been detected"); 107 | 108 | // get a handle to Steam's UGC module (user-generated content) 109 | let ugc = client.ugc(); 110 | 111 | // create a channel to communicate with the upcoming callback thread 112 | // this is technically not *needed* but it is cleaner in order to properly exit the thread 113 | let (tx, rx) = std::sync::mpsc::channel(); 114 | // create a thread for callbacks 115 | // if you have an active loop (like in a game), you can skip this and just run the callbacks on update 116 | let callback_thread = std::thread::spawn(move || { 117 | loop { 118 | // run callbacks 119 | client.run_callbacks(); 120 | std::thread::sleep(std::time::Duration::from_millis(100)); 121 | 122 | // check if the channel is closed or if there is a message 123 | // end the thread if either is true 124 | match rx.try_recv() { 125 | Ok(_) | Err(TryRecvError::Disconnected) => break, 126 | Err(TryRecvError::Empty) => {} 127 | } 128 | } 129 | }); 130 | 131 | create_item(&ugc); 132 | 133 | // only do this once you received a successful callback for creating the item, else this WILL fail! 134 | // and fill the published file ID with an actual ID received from the SteamAPI! 135 | // as said above, for example from your manifest file 136 | upload_item_content(&ugc, PublishedFileId(413)); 137 | 138 | // like above, also only do this with a valid published file ID 139 | delete_item(&ugc, PublishedFileId(413)); 140 | 141 | // close the channel and wait for the callback thread to end 142 | tx.send(()) 143 | .expect("Failed to send message to callback thread"); 144 | callback_thread 145 | .join() 146 | .expect("Failed to join callback thread"); 147 | } 148 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steamnetworkingfakeip.h: -------------------------------------------------------------------------------- 1 | //====== Copyright Valve Corporation, All rights reserved. ==================== 2 | 3 | #ifndef STEAMNETWORKINGFAKEIP_H 4 | #define STEAMNETWORKINGFAKEIP_H 5 | #pragma once 6 | 7 | #include "steamnetworkingtypes.h" 8 | #include "steam_api_common.h" 9 | 10 | // It is HIGHLY recommended to limit messages sent via Fake UDP port to this 11 | // value. The purpose of a Fake UDP port is to make porting ordinary ad-hoc UDP 12 | // code easier. Although the real MTU might be higher than this, this particular 13 | // conservative value is chosen so that fragmentation won't be occurring and 14 | // hiding performance problems from you. 15 | constexpr int k_cbSteamNetworkingSocketsFakeUDPPortRecommendedMTU = 1200; 16 | 17 | // Messages larger than this size are not allowed and cannot be sent 18 | // via Fake UDP port. 19 | constexpr int k_cbSteamNetworkingSocketsFakeUDPPortMaxMessageSize = 4096; 20 | 21 | //----------------------------------------------------------------------------- 22 | /// ISteamNetworkingFakeUDPPort 23 | /// 24 | /// Acts like a UDP port, sending and receiving datagrams addressed using 25 | /// FakeIP addresses. 26 | /// 27 | /// See: ISteamNetworkingSockets::CreateFakeUDPPort 28 | 29 | class ISteamNetworkingFakeUDPPort 30 | { 31 | public: 32 | /// Destroy the object and cleanup any internal connections. 33 | /// Note that this function call is not threadsafe with respect 34 | /// to any other method of this interface. (However, in general 35 | /// all other operations are threadsafe with respect to each other.) 36 | virtual void DestroyFakeUDPPort() = 0; 37 | 38 | /// Send a datagram to the specified FakeIP. 39 | /// 40 | /// See ISteamNetworkingSockets::SendMessageToConnection for the meaning of 41 | /// nSendFlags and possible return codes. 42 | /// 43 | /// Notes: 44 | /// - datagrams larger than the underlying MTU are supported, but 45 | /// reliable messages (k_nSteamNetworkingSend_Reliable) are not supported. 46 | /// - You will usually want to use k_nSteamNetworkingSend_NoNagle 47 | /// - k_EResultBusy is returned if this is a "server" port and the global 48 | /// allocation has not yet completed. 49 | /// - k_EResultIPNotFound will be returned if the address is a local/ephemeral 50 | /// address and no existing connection can be found. This can happen if 51 | /// the remote host contacted us without having a global address, and we 52 | /// assigned them a random local address, and then the session with 53 | /// that host timed out. 54 | /// - When initiating communications, the first messages may be sent 55 | /// via backend signaling, or otherwise delayed, while a route is found. 56 | /// Expect the ping time to fluctuate during this period, and it's possible 57 | /// that messages will be delivered out of order (which is also possible with 58 | /// ordinary UDP). 59 | virtual EResult SendMessageToFakeIP( const SteamNetworkingIPAddr &remoteAddress, const void *pData, uint32 cbData, int nSendFlags ) = 0; 60 | 61 | /// Receive messages on the port. 62 | /// 63 | /// Returns the number of messages returned into your array, up to nMaxMessages. 64 | /// 65 | /// SteamNetworkingMessage_t::m_identity in the returned message(s) will always contain 66 | /// a FakeIP. See ISteamNetworkingUtils::GetRealIdentityForFakeIP. 67 | virtual int ReceiveMessages( SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages ) = 0; 68 | 69 | /// Schedule the internal connection for a given peer to be cleaned up in a few seconds. 70 | /// 71 | /// Idle connections automatically time out, and so this is not strictly *necessary*, 72 | /// but if you have reason to believe that you are done talking to a given peer for 73 | /// a while, you can call this to speed up the timeout. If any remaining packets are 74 | /// sent or received from the peer, the cleanup is canceled and the usual timeout 75 | /// value is restored. Thus you will usually call this immediately after sending 76 | /// or receiving application-layer "close connection" packets. 77 | virtual void ScheduleCleanup( const SteamNetworkingIPAddr &remoteAddress ) = 0; 78 | }; 79 | 80 | /// Callback struct used to notify when a connection has changed state 81 | #if defined( VALVE_CALLBACK_PACK_SMALL ) 82 | #pragma pack( push, 4 ) 83 | #elif defined( VALVE_CALLBACK_PACK_LARGE ) 84 | #pragma pack( push, 8 ) 85 | #else 86 | #error "Must define VALVE_CALLBACK_PACK_SMALL or VALVE_CALLBACK_PACK_LARGE" 87 | #endif 88 | 89 | /// A struct used to describe a "fake IP" we have been assigned to 90 | /// use as an identifier. This callback is posted when 91 | /// ISteamNetworkingSoockets::BeginAsyncRequestFakeIP completes. 92 | /// See also ISteamNetworkingSockets::GetFakeIP 93 | struct SteamNetworkingFakeIPResult_t 94 | { 95 | enum { k_iCallback = k_iSteamNetworkingSocketsCallbacks + 3 }; 96 | 97 | /// Status/result of the allocation request. Possible failure values are: 98 | /// - k_EResultBusy - you called GetFakeIP but the request has not completed. 99 | /// - k_EResultInvalidParam - you called GetFakeIP with an invalid port index 100 | /// - k_EResultLimitExceeded - You asked for too many ports, or made an 101 | /// additional request after one had already succeeded 102 | /// - k_EResultNoMatch - GetFakeIP was called, but no request has been made 103 | /// 104 | /// Note that, with the exception of k_EResultBusy (if you are polling), 105 | /// it is highly recommended to treat all failures as fatal. 106 | EResult m_eResult; 107 | 108 | /// Local identity of the ISteamNetworkingSockets object that made 109 | /// this request and is assigned the IP. This is needed in the callback 110 | /// in the case where there are multiple ISteamNetworkingSockets objects. 111 | /// (E.g. one for the user, and another for the local gameserver). 112 | SteamNetworkingIdentity m_identity; 113 | 114 | /// Fake IPv4 IP address that we have been assigned. NOTE: this 115 | /// IP address is not exclusively ours! Steam tries to avoid sharing 116 | /// IP addresses, but this may not always be possible. The IP address 117 | /// may be currently in use by another host, but with different port(s). 118 | /// The exact same IP:port address may have been used previously. 119 | /// Steam tries to avoid reusing ports until they have not been in use for 120 | /// some time, but this may not always be possible. 121 | uint32 m_unIP; 122 | 123 | /// Port number(s) assigned to us. Only the first entries will contain 124 | /// nonzero values. Entries corresponding to ports beyond what was 125 | /// allocated for you will be zero. 126 | /// 127 | /// (NOTE: At the time of this writing, the maximum number of ports you may 128 | /// request is 4.) 129 | enum { k_nMaxReturnPorts = 8 }; 130 | uint16 m_unPorts[k_nMaxReturnPorts]; 131 | }; 132 | 133 | #pragma pack( pop ) 134 | 135 | #endif // _H 136 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// An id for a steam app/game 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 5 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 6 | pub struct AppId(pub u32); 7 | impl From for AppId { 8 | fn from(id: u32) -> Self { 9 | AppId(id) 10 | } 11 | } 12 | 13 | /// Access to the steam apps interface 14 | pub struct Apps { 15 | pub(crate) apps: *mut sys::ISteamApps, 16 | pub(crate) _inner: Arc, 17 | } 18 | 19 | impl Apps { 20 | /// Returns whether the user currently has the app with the given 21 | /// ID currently installed. 22 | /// 23 | /// This does not mean the user owns the game. 24 | pub fn is_app_installed(&self, app_id: AppId) -> bool { 25 | unsafe { sys::SteamAPI_ISteamApps_BIsAppInstalled(self.apps, app_id.0) } 26 | } 27 | 28 | /// Returns whether the user owns the specific dlc and has it 29 | /// installed. 30 | pub fn is_dlc_installed(&self, app_id: AppId) -> bool { 31 | unsafe { sys::SteamAPI_ISteamApps_BIsDlcInstalled(self.apps, app_id.0) } 32 | } 33 | 34 | /// Returns whether the user is subscribed to the app with the given 35 | /// ID. 36 | /// 37 | /// This should only be used to check ownership of a game related to 38 | /// yours (e.g. demo). 39 | pub fn is_subscribed_app(&self, app_id: AppId) -> bool { 40 | unsafe { sys::SteamAPI_ISteamApps_BIsSubscribedApp(self.apps, app_id.0) } 41 | } 42 | 43 | /// Returns whether the user is subscribed via a free weekend 44 | pub fn is_subscribed_from_free_weekend(&self) -> bool { 45 | unsafe { sys::SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend(self.apps) } 46 | } 47 | 48 | /// Returns whether the user has a VAC ban on their account. 49 | pub fn is_vac_banned(&self) -> bool { 50 | unsafe { sys::SteamAPI_ISteamApps_BIsVACBanned(self.apps) } 51 | } 52 | 53 | /// Returns whether the license for the current app ID 54 | /// is for cyber cafes. 55 | pub fn is_cybercafe(&self) -> bool { 56 | unsafe { sys::SteamAPI_ISteamApps_BIsCybercafe(self.apps) } 57 | } 58 | 59 | /// Returns whether the license for the current app ID 60 | /// provides low violence depots. 61 | pub fn is_low_violence(&self) -> bool { 62 | unsafe { sys::SteamAPI_ISteamApps_BIsLowViolence(self.apps) } 63 | } 64 | 65 | /// Returns whether the user is subscribed to the current app ID 66 | pub fn is_subscribed(&self) -> bool { 67 | unsafe { sys::SteamAPI_ISteamApps_BIsSubscribed(self.apps) } 68 | } 69 | 70 | /// Returns the build id of this app. 71 | pub fn app_build_id(&self) -> i32 { 72 | unsafe { sys::SteamAPI_ISteamApps_GetAppBuildId(self.apps) as i32 } 73 | } 74 | 75 | /// Returns the installation folder of the app with the given ID. 76 | /// 77 | /// This works even if the app isn't installed, returning where it 78 | /// would be installed in the default location. 79 | pub fn app_install_dir(&self, app_id: AppId) -> String { 80 | unsafe { 81 | let mut buffer = vec![0; 2048]; 82 | sys::SteamAPI_ISteamApps_GetAppInstallDir( 83 | self.apps, 84 | app_id.0, 85 | buffer.as_mut_ptr(), 86 | buffer.len() as u32, 87 | ); 88 | let path = CStr::from_ptr(buffer.as_ptr()); 89 | path.to_string_lossy().into_owned() 90 | } 91 | } 92 | 93 | /// Returns the steam id of the original owner of the app. 94 | /// 95 | /// Differs from the current user if the app is borrowed. 96 | pub fn app_owner(&self) -> SteamId { 97 | unsafe { SteamId(sys::SteamAPI_ISteamApps_GetAppOwner(self.apps)) } 98 | } 99 | 100 | /// Returns a list of languages that the current app supports. 101 | pub fn available_game_languages(&self) -> Vec { 102 | unsafe { 103 | let langs = sys::SteamAPI_ISteamApps_GetAvailableGameLanguages(self.apps); 104 | let langs = CStr::from_ptr(langs); 105 | let langs = langs.to_string_lossy(); 106 | langs.split(',').map(|v| v.to_owned()).collect() 107 | } 108 | } 109 | 110 | /// Returns the language the user has set for the current game. 111 | /// 112 | /// If the language hasn't been set this returns the language 113 | /// used for the steam UI. 114 | pub fn current_game_language(&self) -> String { 115 | unsafe { 116 | let lang = sys::SteamAPI_ISteamApps_GetCurrentGameLanguage(self.apps); 117 | let lang = CStr::from_ptr(lang); 118 | lang.to_string_lossy().into_owned() 119 | } 120 | } 121 | 122 | /// Returns the current beta name if any. 123 | /// 124 | /// If the user isn't playing on a beta branch then this 125 | /// returns `None` 126 | pub fn current_beta_name(&self) -> Option { 127 | unsafe { 128 | let mut buffer = vec![0; 256]; 129 | if sys::SteamAPI_ISteamApps_GetCurrentBetaName( 130 | self.apps, 131 | buffer.as_mut_ptr(), 132 | buffer.len() as _, 133 | ) { 134 | let path = CStr::from_ptr(buffer.as_ptr()); 135 | Some(path.to_string_lossy().into_owned()) 136 | } else { 137 | None 138 | } 139 | } 140 | } 141 | 142 | /// Returns the command line if the game was launched via Steam URL 143 | /// 144 | /// If the game was not launched through Steam URL, this returns an empty string. 145 | /// 146 | /// See [Steam API](https://partner.steamgames.com/doc/api/ISteamApps#GetLaunchCommandLine) 147 | pub fn launch_command_line(&self) -> String { 148 | unsafe { 149 | let mut buffer = vec![0; 256]; 150 | let _bytes = sys::SteamAPI_ISteamApps_GetLaunchCommandLine( 151 | self.apps, 152 | buffer.as_mut_ptr(), 153 | buffer.len() as _, 154 | ); 155 | let command_line = CStr::from_ptr(buffer.as_ptr()); 156 | command_line.to_string_lossy().into_owned() 157 | } 158 | } 159 | 160 | /// Gets the associated launch parameter if the game is run via steam://run//?param1=value1;param2=value2;param3=value3 etc. 161 | /// 162 | /// Parameter names starting with the character '@' are reserved for internal use and will always return an empty string. 163 | /// Parameter names starting with an underscore '_' are reserved for steam features -- they can be queried by the game, but it is advised that you don't use param names beginning with an underscore for your own features. 164 | /// 165 | /// See [Steam API](https://partner.steamgames.com/doc/api/ISteamApps#GetLaunchQueryParam) 166 | pub fn launch_query_param(&self, key: &str) -> String { 167 | let key = CString::new(key).unwrap(); 168 | unsafe { 169 | let value = sys::SteamAPI_ISteamApps_GetLaunchQueryParam(self.apps, key.as_ptr()); 170 | let value = CStr::from_ptr(value); 171 | value.to_string_lossy().into_owned() 172 | } 173 | } 174 | } 175 | 176 | /// Called after the user executes a steam url with command line or query parameters such as steam://run///?param1=value1;param2=value2;param3=value3; while the game is already running. 177 | #[derive(Clone, Debug)] 178 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 179 | pub struct NewUrlLaunchParameters; 180 | 181 | impl_callback!(_cb: NewUrlLaunchParameters_t => NewUrlLaunchParameters { 182 | Self 183 | }); 184 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamdualsense.h: -------------------------------------------------------------------------------- 1 | /* SIE CONFIDENTIAL 2 | * $PSLibId$ 3 | * Copyright (C) 2019 Sony Interactive Entertainment Inc. 4 | * All Rights Reserved. 5 | */ 6 | 7 | 8 | #ifndef _SCE_PAD_TRIGGER_EFFECT_H 9 | #define _SCE_PAD_TRIGGER_EFFECT_H 10 | 11 | 12 | #define SCE_PAD_TRIGGER_EFFECT_TRIGGER_MASK_L2 0x01 13 | #define SCE_PAD_TRIGGER_EFFECT_TRIGGER_MASK_R2 0x02 14 | 15 | #define SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_L2 0 16 | #define SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2 1 17 | 18 | #define SCE_PAD_TRIGGER_EFFECT_TRIGGER_NUM 2 19 | 20 | /* Definition of control point num */ 21 | #define SCE_PAD_TRIGGER_EFFECT_CONTROL_POINT_NUM 10 22 | 23 | typedef enum ScePadTriggerEffectMode{ 24 | SCE_PAD_TRIGGER_EFFECT_MODE_OFF, 25 | SCE_PAD_TRIGGER_EFFECT_MODE_FEEDBACK, 26 | SCE_PAD_TRIGGER_EFFECT_MODE_WEAPON, 27 | SCE_PAD_TRIGGER_EFFECT_MODE_VIBRATION, 28 | SCE_PAD_TRIGGER_EFFECT_MODE_MULTIPLE_POSITION_FEEDBACK, 29 | SCE_PAD_TRIGGER_EFFECT_MODE_SLOPE_FEEDBACK, 30 | SCE_PAD_TRIGGER_EFFECT_MODE_MULTIPLE_POSITION_VIBRATION, 31 | } ScePadTriggerEffectMode; 32 | 33 | /** 34 | *E 35 | * @brief parameter for setting the trigger effect to off mode. 36 | * Off Mode: Stop trigger effect. 37 | **/ 38 | typedef struct ScePadTriggerEffectOffParam{ 39 | uint8_t padding[48]; 40 | } ScePadTriggerEffectOffParam; 41 | 42 | /** 43 | *E 44 | * @brief parameter for setting the trigger effect to Feedback mode. 45 | * Feedback Mode: The motor arm pushes back trigger. 46 | * Trigger obtains stiffness at specified position. 47 | **/ 48 | typedef struct ScePadTriggerEffectFeedbackParam{ 49 | uint8_t position; /*E position where the strength of target trigger start changing(0~9). */ 50 | uint8_t strength; /*E strength that the motor arm pushes back target trigger(0~8 (0: Same as Off mode)). */ 51 | uint8_t padding[46]; 52 | } ScePadTriggerEffectFeedbackParam; 53 | 54 | /** 55 | *E 56 | * @brief parameter for setting the trigger effect to Weapon mode. 57 | * Weapon Mode: Emulate weapon like gun trigger. 58 | **/ 59 | typedef struct ScePadTriggerEffectWeaponParam{ 60 | uint8_t startPosition; /*E position where the stiffness of trigger start changing(2~7). */ 61 | uint8_t endPosition; /*E position where the stiffness of trigger finish changing(startPosition+1~8). */ 62 | uint8_t strength; /*E strength of gun trigger(0~8 (0: Same as Off mode)). */ 63 | uint8_t padding[45]; 64 | } ScePadTriggerEffectWeaponParam; 65 | 66 | /** 67 | *E 68 | * @brief parameter for setting the trigger effect to Vibration mode. 69 | * Vibration Mode: Vibrates motor arm around specified position. 70 | **/ 71 | typedef struct ScePadTriggerEffectVibrationParam{ 72 | uint8_t position; /*E position where the motor arm start vibrating(0~9). */ 73 | uint8_t amplitude; /*E vibration amplitude(0~8 (0: Same as Off mode)). */ 74 | uint8_t frequency; /*E vibration frequency(0~255[Hz] (0: Same as Off mode)). */ 75 | uint8_t padding[45]; 76 | } ScePadTriggerEffectVibrationParam; 77 | 78 | /** 79 | *E 80 | * @brief parameter for setting the trigger effect to ScePadTriggerEffectMultiplePositionFeedbackParam mode. 81 | * Multi Position Feedback Mode: The motor arm pushes back trigger. 82 | * Trigger obtains specified stiffness at each control point. 83 | **/ 84 | typedef struct ScePadTriggerEffectMultiplePositionFeedbackParam{ 85 | uint8_t strength[SCE_PAD_TRIGGER_EFFECT_CONTROL_POINT_NUM]; /*E strength that the motor arm pushes back target trigger at position(0~8 (0: Same as Off mode)). 86 | * strength[0] means strength of motor arm at position0. 87 | * strength[1] means strength of motor arm at position1. 88 | * ... 89 | * */ 90 | uint8_t padding[38]; 91 | } ScePadTriggerEffectMultiplePositionFeedbackParam; 92 | 93 | /** 94 | *E 95 | * @brief parameter for setting the trigger effect to Feedback3 mode. 96 | * Slope Feedback Mode: The motor arm pushes back trigger between two spedified control points. 97 | * Stiffness of the trigger is changing depending on the set place. 98 | **/ 99 | typedef struct ScePadTriggerEffectSlopeFeedbackParam{ 100 | 101 | uint8_t startPosition; /*E position where the strength of target trigger start changing(0~endPosition). */ 102 | uint8_t endPosition; /*E position where the strength of target trigger finish changing(startPosition+1~9). */ 103 | uint8_t startStrength; /*E strength when trigger's position is startPosition(1~8) */ 104 | uint8_t endStrength; /*E strength when trigger's position is endPosition(1~8) */ 105 | uint8_t padding[44]; 106 | } ScePadTriggerEffectSlopeFeedbackParam; 107 | 108 | /** 109 | *E 110 | * @brief parameter for setting the trigger effect to Vibration2 mode. 111 | * Multi Position Vibration Mode: Vibrates motor arm around specified control point. 112 | * Trigger vibrates specified amplitude at each control point. 113 | **/ 114 | typedef struct ScePadTriggerEffectMultiplePositionVibrationParam{ 115 | uint8_t frequency; /*E vibration frequency(0~255 (0: Same as Off mode)) */ 116 | uint8_t amplitude[SCE_PAD_TRIGGER_EFFECT_CONTROL_POINT_NUM]; /*E vibration amplitude at position(0~8 (0: Same as Off mode)). 117 | * amplitude[0] means amplitude of vibration at position0. 118 | * amplitude[1] means amplitude of vibration at position1. 119 | * ... 120 | * */ 121 | uint8_t padding[37]; 122 | } ScePadTriggerEffectMultiplePositionVibrationParam; 123 | 124 | /** 125 | *E 126 | * @brief parameter for setting the trigger effect mode. 127 | **/ 128 | typedef union ScePadTriggerEffectCommandData{ 129 | ScePadTriggerEffectOffParam offParam; 130 | ScePadTriggerEffectFeedbackParam feedbackParam; 131 | ScePadTriggerEffectWeaponParam weaponParam; 132 | ScePadTriggerEffectVibrationParam vibrationParam; 133 | ScePadTriggerEffectMultiplePositionFeedbackParam multiplePositionFeedbackParam; 134 | ScePadTriggerEffectSlopeFeedbackParam slopeFeedbackParam; 135 | ScePadTriggerEffectMultiplePositionVibrationParam multiplePositionVibrationParam; 136 | } ScePadTriggerEffectCommandData; 137 | 138 | /** 139 | *E 140 | * @brief parameter for setting the trigger effect. 141 | **/ 142 | typedef struct ScePadTriggerEffectCommand{ 143 | ScePadTriggerEffectMode mode; 144 | uint8_t padding[4]; 145 | ScePadTriggerEffectCommandData commandData; 146 | } ScePadTriggerEffectCommand; 147 | 148 | /** 149 | *E 150 | * @brief parameter for the scePadSetTriggerEffect function. 151 | **/ 152 | typedef struct ScePadTriggerEffectParam{ 153 | 154 | uint8_t triggerMask; /*E Set trigger mask to activate trigger effect commands. 155 | * SCE_PAD_TRIGGER_EFFECT_TRIGGER_MASK_L2 : 0x01 156 | * SCE_PAD_TRIGGER_EFFECT_TRIGGER_MASK_R2 : 0x02 157 | * */ 158 | uint8_t padding[7]; 159 | 160 | ScePadTriggerEffectCommand command[SCE_PAD_TRIGGER_EFFECT_TRIGGER_NUM]; /*E command[SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_L2] is for L2 trigger setting 161 | * and param[SCE_PAD_TRIGGER_EFFECT_PARAM_INDEX_FOR_R2] is for R2 trgger setting. 162 | * */ 163 | } ScePadTriggerEffectParam; 164 | 165 | #if defined(__cplusplus) && __cplusplus >= 201103L 166 | static_assert( sizeof( ScePadTriggerEffectParam ) == 120, "ScePadTriggerEffectParam has incorrect size" ); 167 | #endif 168 | 169 | #endif /* _SCE_PAD_TRIGGER_EFFECT_H */ 170 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/matchmakingtypes.h: -------------------------------------------------------------------------------- 1 | //========= Copyright � 1996-2008, Valve LLC, All rights reserved. ============ 2 | // 3 | // Purpose: 4 | // 5 | // $NoKeywords: $ 6 | //============================================================================= 7 | 8 | #ifndef MATCHMAKINGTYPES_H 9 | #define MATCHMAKINGTYPES_H 10 | 11 | #include 12 | #include 13 | 14 | // 15 | // Max size (in bytes of UTF-8 data, not in characters) of server fields, including null terminator. 16 | // WARNING: These cannot be changed easily, without breaking clients using old interfaces. 17 | // 18 | const int k_cbMaxGameServerGameDir = 32; 19 | const int k_cbMaxGameServerMapName = 32; 20 | const int k_cbMaxGameServerGameDescription = 64; 21 | const int k_cbMaxGameServerName = 64; 22 | const int k_cbMaxGameServerTags = 128; 23 | const int k_cbMaxGameServerGameData = 2048; 24 | 25 | /// Store key/value pair used in matchmaking queries. 26 | /// 27 | /// Actually, the name Key/Value is a bit misleading. The "key" is better 28 | /// understood as "filter operation code" and the "value" is the operand to this 29 | /// filter operation. The meaning of the operand depends upon the filter. 30 | struct MatchMakingKeyValuePair_t 31 | { 32 | MatchMakingKeyValuePair_t() { m_szKey[0] = m_szValue[0] = 0; } 33 | MatchMakingKeyValuePair_t( const char *pchKey, const char *pchValue ) 34 | { 35 | strncpy( m_szKey, pchKey, sizeof(m_szKey) ); // this is a public header, use basic c library string funcs only! 36 | m_szKey[ sizeof( m_szKey ) - 1 ] = '\0'; 37 | strncpy( m_szValue, pchValue, sizeof(m_szValue) ); 38 | m_szValue[ sizeof( m_szValue ) - 1 ] = '\0'; 39 | } 40 | char m_szKey[ 256 ]; 41 | char m_szValue[ 256 ]; 42 | }; 43 | 44 | 45 | enum EMatchMakingServerResponse 46 | { 47 | eServerResponded = 0, 48 | eServerFailedToRespond, 49 | eNoServersListedOnMasterServer // for the Internet query type, returned in response callback if no servers of this type match 50 | }; 51 | 52 | // servernetadr_t is all the addressing info the serverbrowser needs to know about a game server, 53 | // namely: its IP, its connection port, and its query port. 54 | class servernetadr_t 55 | { 56 | public: 57 | 58 | servernetadr_t() : m_usConnectionPort( 0 ), m_usQueryPort( 0 ), m_unIP( 0 ) {} 59 | 60 | void Init( unsigned int ip, uint16 usQueryPort, uint16 usConnectionPort ); 61 | 62 | // Access the query port. 63 | uint16 GetQueryPort() const; 64 | void SetQueryPort( uint16 usPort ); 65 | 66 | // Access the connection port. 67 | uint16 GetConnectionPort() const; 68 | void SetConnectionPort( uint16 usPort ); 69 | 70 | // Access the IP 71 | uint32 GetIP() const; 72 | void SetIP( uint32 unIP ); 73 | 74 | // This gets the 'a.b.c.d:port' string with the connection port (instead of the query port). 75 | const char *GetConnectionAddressString() const; 76 | const char *GetQueryAddressString() const; 77 | 78 | // Comparison operators and functions. 79 | bool operator<(const servernetadr_t &netadr) const; 80 | void operator=( const servernetadr_t &that ) 81 | { 82 | m_usConnectionPort = that.m_usConnectionPort; 83 | m_usQueryPort = that.m_usQueryPort; 84 | m_unIP = that.m_unIP; 85 | } 86 | 87 | 88 | private: 89 | const char *ToString( uint32 unIP, uint16 usPort ) const; 90 | uint16 m_usConnectionPort; // (in HOST byte order) 91 | uint16 m_usQueryPort; 92 | uint32 m_unIP; 93 | }; 94 | 95 | 96 | inline void servernetadr_t::Init( unsigned int ip, uint16 usQueryPort, uint16 usConnectionPort ) 97 | { 98 | m_unIP = ip; 99 | m_usQueryPort = usQueryPort; 100 | m_usConnectionPort = usConnectionPort; 101 | } 102 | 103 | inline uint16 servernetadr_t::GetQueryPort() const 104 | { 105 | return m_usQueryPort; 106 | } 107 | 108 | inline void servernetadr_t::SetQueryPort( uint16 usPort ) 109 | { 110 | m_usQueryPort = usPort; 111 | } 112 | 113 | inline uint16 servernetadr_t::GetConnectionPort() const 114 | { 115 | return m_usConnectionPort; 116 | } 117 | 118 | inline void servernetadr_t::SetConnectionPort( uint16 usPort ) 119 | { 120 | m_usConnectionPort = usPort; 121 | } 122 | 123 | inline uint32 servernetadr_t::GetIP() const 124 | { 125 | return m_unIP; 126 | } 127 | 128 | inline void servernetadr_t::SetIP( uint32 unIP ) 129 | { 130 | m_unIP = unIP; 131 | } 132 | 133 | inline const char *servernetadr_t::ToString( uint32 unIP, uint16 usPort ) const 134 | { 135 | static char s[4][64]; 136 | static int nBuf = 0; 137 | unsigned char *ipByte = (unsigned char *)&unIP; 138 | #ifdef VALVE_BIG_ENDIAN 139 | snprintf(s[nBuf], sizeof( s[nBuf] ), "%u.%u.%u.%u:%i", (int)(ipByte[0]), (int)(ipByte[1]), (int)(ipByte[2]), (int)(ipByte[3]), usPort ); 140 | #else 141 | snprintf(s[nBuf], sizeof( s[nBuf] ), "%u.%u.%u.%u:%i", (int)(ipByte[3]), (int)(ipByte[2]), (int)(ipByte[1]), (int)(ipByte[0]), usPort ); 142 | #endif 143 | const char *pchRet = s[nBuf]; 144 | ++nBuf; 145 | nBuf %= ( (sizeof(s)/sizeof(s[0])) ); 146 | return pchRet; 147 | } 148 | 149 | inline const char* servernetadr_t::GetConnectionAddressString() const 150 | { 151 | return ToString( m_unIP, m_usConnectionPort ); 152 | } 153 | 154 | inline const char* servernetadr_t::GetQueryAddressString() const 155 | { 156 | return ToString( m_unIP, m_usQueryPort ); 157 | } 158 | 159 | inline bool servernetadr_t::operator<(const servernetadr_t &netadr) const 160 | { 161 | return ( m_unIP < netadr.m_unIP ) || ( m_unIP == netadr.m_unIP && m_usQueryPort < netadr.m_usQueryPort ); 162 | } 163 | 164 | //----------------------------------------------------------------------------- 165 | // Purpose: Data describing a single server 166 | //----------------------------------------------------------------------------- 167 | class gameserveritem_t 168 | { 169 | public: 170 | gameserveritem_t(); 171 | 172 | const char* GetName() const; 173 | void SetName( const char *pName ); 174 | 175 | public: 176 | servernetadr_t m_NetAdr; ///< IP/Query Port/Connection Port for this server 177 | int m_nPing; ///< current ping time in milliseconds 178 | bool m_bHadSuccessfulResponse; ///< server has responded successfully in the past 179 | bool m_bDoNotRefresh; ///< server is marked as not responding and should no longer be refreshed 180 | char m_szGameDir[k_cbMaxGameServerGameDir]; ///< current game directory 181 | char m_szMap[k_cbMaxGameServerMapName]; ///< current map 182 | char m_szGameDescription[k_cbMaxGameServerGameDescription]; ///< game description 183 | uint32 m_nAppID; ///< Steam App ID of this server 184 | int m_nPlayers; ///< total number of players currently on the server. INCLUDES BOTS!! 185 | int m_nMaxPlayers; ///< Maximum players that can join this server 186 | int m_nBotPlayers; ///< Number of bots (i.e simulated players) on this server 187 | bool m_bPassword; ///< true if this server needs a password to join 188 | bool m_bSecure; ///< Is this server protected by VAC 189 | uint32 m_ulTimeLastPlayed; ///< time (in unix time) when this server was last played on (for favorite/history servers) 190 | int m_nServerVersion; ///< server version as reported to Steam 191 | 192 | private: 193 | 194 | /// Game server name 195 | char m_szServerName[k_cbMaxGameServerName]; 196 | 197 | // For data added after SteamMatchMaking001 add it here 198 | public: 199 | /// the tags this server exposes 200 | char m_szGameTags[k_cbMaxGameServerTags]; 201 | 202 | /// steamID of the game server - invalid if it's doesn't have one (old server, or not connected to Steam) 203 | CSteamID m_steamID; 204 | }; 205 | 206 | 207 | inline gameserveritem_t::gameserveritem_t() 208 | { 209 | m_szGameDir[0] = m_szMap[0] = m_szGameDescription[0] = m_szServerName[0] = 0; 210 | m_bHadSuccessfulResponse = m_bDoNotRefresh = m_bPassword = m_bSecure = false; 211 | m_nPing = m_nAppID = m_nPlayers = m_nMaxPlayers = m_nBotPlayers = m_ulTimeLastPlayed = m_nServerVersion = 0; 212 | m_szGameTags[0] = 0; 213 | } 214 | 215 | inline const char* gameserveritem_t::GetName() const 216 | { 217 | // Use the IP address as the name if nothing is set yet. 218 | if ( m_szServerName[0] == 0 ) 219 | return m_NetAdr.GetConnectionAddressString(); 220 | else 221 | return m_szServerName; 222 | } 223 | 224 | inline void gameserveritem_t::SetName( const char *pName ) 225 | { 226 | strncpy( m_szServerName, pName, sizeof( m_szServerName ) ); 227 | m_szServerName[ sizeof( m_szServerName ) - 1 ] = '\0'; 228 | } 229 | 230 | 231 | #endif // MATCHMAKINGTYPES_H 232 | -------------------------------------------------------------------------------- /src/callback.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::networking_messages::*; 3 | use crate::networking_types::*; 4 | use crate::networking_utils::*; 5 | use crate::screenshots::*; 6 | 7 | use crate::sys; 8 | 9 | use std::sync::{Arc, Weak}; 10 | 11 | /// A sum type over all possible callback results 12 | #[derive(Debug)] 13 | pub enum CallbackResult { 14 | AuthSessionTicketResponse(AuthSessionTicketResponse), 15 | DownloadItemResult(DownloadItemResult), 16 | FloatingGamepadTextInputDismissed(FloatingGamepadTextInputDismissed), 17 | GameLobbyJoinRequested(GameLobbyJoinRequested), 18 | GameOverlayActivated(GameOverlayActivated), 19 | GamepadTextInputDismissed(GamepadTextInputDismissed), 20 | GameRichPresenceJoinRequested(GameRichPresenceJoinRequested), 21 | LobbyChatMsg(LobbyChatMsg), 22 | LobbyChatUpdate(LobbyChatUpdate), 23 | LobbyCreated(LobbyCreated), 24 | LobbyDataUpdate(LobbyDataUpdate), 25 | LobbyEnter(LobbyEnter), 26 | MicroTxnAuthorizationResponse(MicroTxnAuthorizationResponse), 27 | NetConnectionStatusChanged(NetConnectionStatusChanged), 28 | NetworkingMessagesSessionFailed(NetworkingMessagesSessionFailed), 29 | NetworkingMessagesSessionRequest(NetworkingMessagesSessionRequest), 30 | P2PSessionConnectFail(P2PSessionConnectFail), 31 | P2PSessionRequest(P2PSessionRequest), 32 | PersonaStateChange(PersonaStateChange), 33 | RelayNetworkStatusCallback(RelayNetworkStatusCallback), 34 | RemotePlayConnected(RemotePlayConnected), 35 | RemotePlayDisconnected(RemotePlayDisconnected), 36 | ScreenshotRequested(ScreenshotRequested), 37 | ScreenshotReady(ScreenshotReady), 38 | SteamServerConnectFailure(SteamServerConnectFailure), 39 | SteamServersConnected(SteamServersConnected), 40 | SteamServersDisconnected(SteamServersDisconnected), 41 | TicketForWebApiResponse(TicketForWebApiResponse), 42 | UserAchievementStored(UserAchievementStored), 43 | UserAchievementIconFetched(UserAchievementIconFetched), 44 | UserStatsReceived(UserStatsReceived), 45 | UserStatsStored(UserStatsStored), 46 | ValidateAuthTicketResponse(ValidateAuthTicketResponse), 47 | GSClientApprove(GSClientApprove), 48 | GSClientDeny(GSClientDeny), 49 | GSClientKick(GSClientKick), 50 | GSClientGroupStatus(GSClientGroupStatus), 51 | NewUrlLaunchParameters(NewUrlLaunchParameters), 52 | } 53 | 54 | impl CallbackResult { 55 | pub unsafe fn from_raw(discriminator: i32, data: *mut c_void) -> Option { 56 | Some(match discriminator { 57 | NetConnectionStatusChanged::ID => { 58 | Self::NetConnectionStatusChanged(NetConnectionStatusChanged::from_raw(data)) 59 | } 60 | AuthSessionTicketResponse::ID => { 61 | Self::AuthSessionTicketResponse(AuthSessionTicketResponse::from_raw(data)) 62 | } 63 | DownloadItemResult::ID => Self::DownloadItemResult(DownloadItemResult::from_raw(data)), 64 | FloatingGamepadTextInputDismissed::ID => Self::FloatingGamepadTextInputDismissed( 65 | FloatingGamepadTextInputDismissed::from_raw(data), 66 | ), 67 | GameLobbyJoinRequested::ID => { 68 | Self::GameLobbyJoinRequested(GameLobbyJoinRequested::from_raw(data)) 69 | } 70 | GameOverlayActivated::ID => { 71 | Self::GameOverlayActivated(GameOverlayActivated::from_raw(data)) 72 | } 73 | GamepadTextInputDismissed::ID => { 74 | Self::GamepadTextInputDismissed(GamepadTextInputDismissed::from_raw(data)) 75 | } 76 | GameRichPresenceJoinRequested::ID => { 77 | Self::GameRichPresenceJoinRequested(GameRichPresenceJoinRequested::from_raw(data)) 78 | } 79 | LobbyChatMsg::ID => Self::LobbyChatMsg(LobbyChatMsg::from_raw(data)), 80 | LobbyDataUpdate::ID => Self::LobbyDataUpdate(LobbyDataUpdate::from_raw(data)), 81 | MicroTxnAuthorizationResponse::ID => { 82 | Self::MicroTxnAuthorizationResponse(MicroTxnAuthorizationResponse::from_raw(data)) 83 | } 84 | P2PSessionConnectFail::ID => { 85 | Self::P2PSessionConnectFail(P2PSessionConnectFail::from_raw(data)) 86 | } 87 | P2PSessionRequest::ID => Self::P2PSessionRequest(P2PSessionRequest::from_raw(data)), 88 | PersonaStateChange::ID => Self::PersonaStateChange(PersonaStateChange::from_raw(data)), 89 | RemotePlayConnected::ID => { 90 | Self::RemotePlayConnected(RemotePlayConnected::from_raw(data)) 91 | } 92 | RemotePlayDisconnected::ID => { 93 | Self::RemotePlayDisconnected(RemotePlayDisconnected::from_raw(data)) 94 | } 95 | SteamServerConnectFailure::ID => { 96 | Self::SteamServerConnectFailure(SteamServerConnectFailure::from_raw(data)) 97 | } 98 | SteamServersConnected::ID => { 99 | Self::SteamServersConnected(SteamServersConnected::from_raw(data)) 100 | } 101 | SteamServersDisconnected::ID => { 102 | Self::SteamServersDisconnected(SteamServersDisconnected::from_raw(data)) 103 | } 104 | TicketForWebApiResponse::ID => { 105 | Self::TicketForWebApiResponse(TicketForWebApiResponse::from_raw(data)) 106 | } 107 | UserAchievementStored::ID => { 108 | Self::UserAchievementStored(UserAchievementStored::from_raw(data)) 109 | } 110 | UserStatsReceived::ID => Self::UserStatsReceived(UserStatsReceived::from_raw(data)), 111 | UserStatsStored::ID => Self::UserStatsStored(UserStatsStored::from_raw(data)), 112 | ValidateAuthTicketResponse::ID => { 113 | Self::ValidateAuthTicketResponse(ValidateAuthTicketResponse::from_raw(data)) 114 | } 115 | GSClientApprove::ID => Self::GSClientApprove(GSClientApprove::from_raw(data)), 116 | GSClientDeny::ID => Self::GSClientDeny(GSClientDeny::from_raw(data)), 117 | GSClientKick::ID => Self::GSClientKick(GSClientKick::from_raw(data)), 118 | GSClientGroupStatus::ID => { 119 | Self::GSClientGroupStatus(GSClientGroupStatus::from_raw(data)) 120 | } 121 | NewUrlLaunchParameters::ID => { 122 | Self::NewUrlLaunchParameters(NewUrlLaunchParameters::from_raw(data)) 123 | } 124 | _ => return None, 125 | }) 126 | } 127 | } 128 | 129 | pub unsafe trait Callback { 130 | const ID: i32; 131 | unsafe fn from_raw(raw: *mut c_void) -> Self; 132 | } 133 | 134 | /// A handle that can be used to remove a callback 135 | /// at a later point. 136 | /// 137 | /// Removes the callback from the Steam API context when dropped. 138 | pub struct CallbackHandle { 139 | id: i32, 140 | inner: Weak, 141 | } 142 | 143 | impl Drop for CallbackHandle { 144 | fn drop(&mut self) { 145 | if let Some(inner) = self.inner.upgrade() { 146 | match inner.callbacks.callbacks.lock() { 147 | Ok(mut cb) => { 148 | cb.remove(&self.id); 149 | } 150 | Err(err) => { 151 | eprintln!("error while dropping callback: {:?}", err); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | macro_rules! impl_callback { 159 | ($fn_arg_name:ident: $sys_ty:ident => $callback_ty:ident $from_raw_impl:tt) => { 160 | paste::item! { 161 | unsafe impl Callback for $callback_ty { 162 | const ID: i32 = steamworks_sys::[<$sys_ty _ k_iCallback>] as i32; 163 | 164 | unsafe fn from_raw(raw: *mut c_void) -> Self { 165 | let $fn_arg_name = raw.cast::().read_unaligned(); 166 | $from_raw_impl 167 | } 168 | } 169 | } 170 | }; 171 | } 172 | 173 | pub(crate) unsafe fn register_callback(inner: &Arc, mut f: F) -> CallbackHandle 174 | where 175 | C: Callback, 176 | F: FnMut(C) + Send + 'static, 177 | { 178 | { 179 | inner.callbacks.callbacks.lock().unwrap().insert( 180 | C::ID, 181 | Box::new(move |param| { 182 | let param = C::from_raw(param); 183 | f(param) 184 | }), 185 | ); 186 | } 187 | CallbackHandle { 188 | id: C::ID, 189 | inner: Arc::downgrade(inner), 190 | } 191 | } 192 | 193 | pub(crate) unsafe fn register_call_result( 194 | inner: &Arc, 195 | api_call: sys::SteamAPICall_t, 196 | f: F, 197 | ) where 198 | F: for<'a> FnOnce(&'a C, bool) + 'static + Send, 199 | { 200 | inner.callbacks.call_results.lock().unwrap().insert( 201 | api_call, 202 | Box::new(move |param, failed| { 203 | let value = param.cast::().read_unaligned(); 204 | f(&value, failed) 205 | }), 206 | ); 207 | } 208 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamclient.h: -------------------------------------------------------------------------------- 1 | //====== Copyright Valve Corporation, All rights reserved. ==================== 2 | // 3 | // Internal low-level access to Steamworks interfaces. 4 | // 5 | // Most users of the Steamworks SDK do not need to include this file. 6 | // You should only include this if you are doing something special. 7 | //============================================================================= 8 | 9 | #ifndef ISTEAMCLIENT_H 10 | #define ISTEAMCLIENT_H 11 | #ifdef _WIN32 12 | #pragma once 13 | #endif 14 | 15 | #include "steam_api_common.h" 16 | 17 | //----------------------------------------------------------------------------- 18 | // Purpose: Interface to creating a new steam instance, or to 19 | // connect to an existing steam instance, whether it's in a 20 | // different process or is local. 21 | // 22 | // For most scenarios this is all handled automatically via SteamAPI_Init(). 23 | // You'll only need these APIs if you have a more complex versioning scheme, 24 | // or if you want to implement a multiplexed gameserver where a single process 25 | // is handling multiple games at once with independent gameserver SteamIDs. 26 | //----------------------------------------------------------------------------- 27 | class ISteamClient 28 | { 29 | public: 30 | // Creates a communication pipe to the Steam client. 31 | // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling 32 | virtual HSteamPipe CreateSteamPipe() = 0; 33 | 34 | // Releases a previously created communications pipe 35 | // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling 36 | virtual bool BReleaseSteamPipe( HSteamPipe hSteamPipe ) = 0; 37 | 38 | // connects to an existing global user, failing if none exists 39 | // used by the game to coordinate with the steamUI 40 | // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling 41 | virtual HSteamUser ConnectToGlobalUser( HSteamPipe hSteamPipe ) = 0; 42 | 43 | // used by game servers, create a steam user that won't be shared with anyone else 44 | // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling 45 | virtual HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType eAccountType ) = 0; 46 | 47 | // removes an allocated user 48 | // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling 49 | virtual void ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) = 0; 50 | 51 | // retrieves the ISteamUser interface associated with the handle 52 | virtual ISteamUser *GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 53 | 54 | // retrieves the ISteamGameServer interface associated with the handle 55 | virtual ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 56 | 57 | // set the local IP and Port to bind to 58 | // this must be set before CreateLocalUser() 59 | virtual void SetLocalIPBinding( const SteamIPAddress_t &unIP, uint16 usPort ) = 0; 60 | 61 | // returns the ISteamFriends interface 62 | virtual ISteamFriends *GetISteamFriends( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 63 | 64 | // returns the ISteamUtils interface 65 | virtual ISteamUtils *GetISteamUtils( HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 66 | 67 | // returns the ISteamMatchmaking interface 68 | virtual ISteamMatchmaking *GetISteamMatchmaking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 69 | 70 | // returns the ISteamMatchmakingServers interface 71 | virtual ISteamMatchmakingServers *GetISteamMatchmakingServers( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 72 | 73 | // returns the a generic interface 74 | virtual void *GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 75 | 76 | // returns the ISteamUserStats interface 77 | virtual ISteamUserStats *GetISteamUserStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 78 | 79 | // returns the ISteamGameServerStats interface 80 | virtual ISteamGameServerStats *GetISteamGameServerStats( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 81 | 82 | // returns apps interface 83 | virtual ISteamApps *GetISteamApps( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 84 | 85 | // networking 86 | virtual ISteamNetworking *GetISteamNetworking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 87 | 88 | // remote storage 89 | virtual ISteamRemoteStorage *GetISteamRemoteStorage( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 90 | 91 | // user screenshots 92 | virtual ISteamScreenshots *GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 93 | 94 | // game search 95 | virtual ISteamGameSearch *GetISteamGameSearch( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 96 | 97 | // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. 98 | STEAM_PRIVATE_API( virtual void RunFrame() = 0; ) 99 | 100 | // returns the number of IPC calls made since the last time this function was called 101 | // Used for perf debugging so you can understand how many IPC calls your game makes per frame 102 | // Every IPC call is at minimum a thread context switch if not a process one so you want to rate 103 | // control how often you do them. 104 | virtual uint32 GetIPCCallCount() = 0; 105 | 106 | // API warning handling 107 | // 'int' is the severity; 0 for msg, 1 for warning 108 | // 'const char *' is the text of the message 109 | // callbacks will occur directly after the API function is called that generated the warning or message. 110 | virtual void SetWarningMessageHook( SteamAPIWarningMessageHook_t pFunction ) = 0; 111 | 112 | // Trigger global shutdown for the DLL 113 | virtual bool BShutdownIfAllPipesClosed() = 0; 114 | 115 | // Expose HTTP interface 116 | virtual ISteamHTTP *GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 117 | 118 | // Exposes the ISteamController interface - deprecated in favor of Steam Input 119 | virtual ISteamController *GetISteamController( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 120 | 121 | // Exposes the ISteamUGC interface 122 | virtual ISteamUGC *GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 123 | 124 | // Music Player 125 | virtual ISteamMusic *GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 126 | 127 | // Music Player Remote 128 | virtual ISteamMusicRemote *GetISteamMusicRemote(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion) = 0; 129 | 130 | // html page display 131 | virtual ISteamHTMLSurface *GetISteamHTMLSurface(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion) = 0; 132 | 133 | // Helper functions for internal Steam usage 134 | STEAM_PRIVATE_API( virtual void DEPRECATED_Set_SteamAPI_CPostAPIResultInProcess( void (*)() ) = 0; ) 135 | STEAM_PRIVATE_API( virtual void DEPRECATED_Remove_SteamAPI_CPostAPIResultInProcess( void (*)() ) = 0; ) 136 | STEAM_PRIVATE_API( virtual void Set_SteamAPI_CCheckCallbackRegisteredInProcess( SteamAPI_CheckCallbackRegistered_t func ) = 0; ) 137 | 138 | // inventory 139 | virtual ISteamInventory *GetISteamInventory( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 140 | 141 | // Video 142 | virtual ISteamVideo *GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 143 | 144 | // Parental controls 145 | virtual ISteamParentalSettings *GetISteamParentalSettings( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 146 | 147 | // Exposes the Steam Input interface for controller support 148 | virtual ISteamInput *GetISteamInput( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 149 | 150 | // Steam Parties interface 151 | virtual ISteamParties *GetISteamParties( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 152 | 153 | // Steam Remote Play interface 154 | virtual ISteamRemotePlay *GetISteamRemotePlay( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) = 0; 155 | 156 | STEAM_PRIVATE_API( virtual void DestroyAllInterfaces() = 0; ) 157 | 158 | }; 159 | #define STEAMCLIENT_INTERFACE_VERSION "SteamClient021" 160 | 161 | #ifndef STEAM_API_EXPORTS 162 | 163 | // Global ISteamClient interface accessor 164 | inline ISteamClient *SteamClient(); 165 | STEAM_DEFINE_INTERFACE_ACCESSOR( ISteamClient *, SteamClient, SteamInternal_CreateInterface( STEAMCLIENT_INTERFACE_VERSION ), "global", STEAMCLIENT_INTERFACE_VERSION ); 166 | 167 | // The internal ISteamClient used for the gameserver interface. 168 | // (This is actually the same thing. You really shouldn't need to access any of this stuff directly.) 169 | inline ISteamClient *SteamGameServerClient() { return SteamClient(); } 170 | 171 | #endif 172 | 173 | #endif // ISTEAMCLIENT_H 174 | -------------------------------------------------------------------------------- /src/networking_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::networking_types::{NetworkingAvailabilityResult, NetworkingMessage}; 2 | use crate::{register_callback, Callback, Inner}; 3 | use std::convert::TryInto; 4 | use std::ffi::{c_void, CStr}; 5 | use std::sync::Arc; 6 | 7 | use steamworks_sys as sys; 8 | 9 | /// Access to the steam networking sockets interface 10 | pub struct NetworkingUtils { 11 | pub(crate) utils: *mut sys::ISteamNetworkingUtils, 12 | pub(crate) inner: Arc, 13 | } 14 | 15 | unsafe impl Send for NetworkingUtils {} 16 | unsafe impl Sync for NetworkingUtils {} 17 | 18 | impl NetworkingUtils { 19 | /// Allocate and initialize a message object. Usually the reason 20 | /// you call this is to pass it to ISteamNetworkingSockets::SendMessages. 21 | /// The returned object will have all of the relevant fields cleared to zero. 22 | /// 23 | /// Optionally you can also request that this system allocate space to 24 | /// hold the payload itself. If cbAllocateBuffer is nonzero, the system 25 | /// will allocate memory to hold a payload of at least cbAllocateBuffer bytes. 26 | /// m_pData will point to the allocated buffer, m_cbSize will be set to the 27 | /// size, and m_pfnFreeData will be set to the proper function to free up 28 | /// the buffer. 29 | /// 30 | /// If cbAllocateBuffer=0, then no buffer is allocated. m_pData will be NULL, 31 | /// m_cbSize will be zero, and m_pfnFreeData will be NULL. You will need to 32 | /// set each of these. 33 | pub fn allocate_message(&self, buffer_size: usize) -> NetworkingMessage { 34 | unsafe { 35 | let message = 36 | sys::SteamAPI_ISteamNetworkingUtils_AllocateMessage(self.utils, buffer_size as _); 37 | NetworkingMessage { 38 | message, 39 | _inner: self.inner.clone(), 40 | } 41 | } 42 | } 43 | 44 | /// If you know that you are going to be using the relay network (for example, 45 | /// because you anticipate making P2P connections), call this to initialize the 46 | /// relay network. If you do not call this, the initialization will 47 | /// be delayed until the first time you use a feature that requires access 48 | /// to the relay network, which will delay that first access. 49 | /// 50 | /// You can also call this to force a retry if the previous attempt has failed. 51 | /// Performing any action that requires access to the relay network will also 52 | /// trigger a retry, and so calling this function is never strictly necessary, 53 | /// but it can be useful to call it a program launch time, if access to the 54 | /// relay network is anticipated. 55 | /// 56 | /// Use GetRelayNetworkStatus or listen for SteamRelayNetworkStatus_t 57 | /// callbacks to know when initialization has completed. 58 | /// Typically initialization completes in a few seconds. 59 | /// 60 | /// Note: dedicated servers hosted in known data centers do *not* need 61 | /// to call this, since they do not make routing decisions. However, if 62 | /// the dedicated server will be using P2P functionality, it will act as 63 | /// a "client" and this should be called. 64 | pub fn init_relay_network_access(&self) { 65 | unsafe { 66 | sys::SteamAPI_ISteamNetworkingUtils_InitRelayNetworkAccess(self.utils); 67 | } 68 | } 69 | 70 | /// Fetch current status of the relay network. 71 | /// 72 | /// If you want more detailed information use [`detailed_relay_network_status`](#method.detailed_relay_network_status) instead. 73 | pub fn relay_network_status(&self) -> NetworkingAvailabilityResult { 74 | unsafe { 75 | sys::SteamAPI_ISteamNetworkingUtils_GetRelayNetworkStatus( 76 | self.utils, 77 | std::ptr::null_mut(), 78 | ) 79 | .try_into() 80 | } 81 | } 82 | 83 | /// Fetch current detailed status of the relay network. 84 | pub fn detailed_relay_network_status(&self) -> RelayNetworkStatus { 85 | unsafe { 86 | let mut status = sys::SteamRelayNetworkStatus_t { 87 | m_eAvail: sys::ESteamNetworkingAvailability::k_ESteamNetworkingAvailability_Unknown, 88 | m_bPingMeasurementInProgress: 0, 89 | m_eAvailNetworkConfig: 90 | sys::ESteamNetworkingAvailability::k_ESteamNetworkingAvailability_Unknown, 91 | m_eAvailAnyRelay: 92 | sys::ESteamNetworkingAvailability::k_ESteamNetworkingAvailability_Unknown, 93 | m_debugMsg: [0; 256], 94 | }; 95 | sys::SteamAPI_ISteamNetworkingUtils_GetRelayNetworkStatus(self.utils, &mut status); 96 | status.into() 97 | } 98 | } 99 | 100 | /// Register the callback for relay network status updates. 101 | /// 102 | /// Calling this more than once replaces the previous callback. 103 | pub fn relay_network_status_callback( 104 | &self, 105 | mut callback: impl FnMut(RelayNetworkStatus) + Send + 'static, 106 | ) { 107 | unsafe { 108 | register_callback(&self.inner, move |status: RelayNetworkStatusCallback| { 109 | callback(status.status); 110 | }); 111 | } 112 | } 113 | } 114 | 115 | #[derive(Debug)] 116 | pub struct RelayNetworkStatus { 117 | availability: NetworkingAvailabilityResult, 118 | is_ping_measurement_in_progress: bool, 119 | network_config: NetworkingAvailabilityResult, 120 | any_relay: NetworkingAvailabilityResult, 121 | 122 | debugging_message: String, 123 | } 124 | 125 | impl RelayNetworkStatus { 126 | /// Summary status. When this is "current", initialization has 127 | /// completed. Anything else means you are not ready yet, or 128 | /// there is a significant problem. 129 | pub fn availability(&self) -> NetworkingAvailabilityResult { 130 | self.availability.clone() 131 | } 132 | 133 | /// True if latency measurement is in progress (or pending, awaiting a prerequisite). 134 | pub fn is_ping_measurement_in_progress(&self) -> bool { 135 | self.is_ping_measurement_in_progress 136 | } 137 | 138 | /// Status obtaining the network config. This is a prerequisite 139 | /// for relay network access. 140 | /// 141 | /// Failure to obtain the network config almost always indicates 142 | /// a problem with the local internet connection. 143 | pub fn network_config(&self) -> NetworkingAvailabilityResult { 144 | self.network_config.clone() 145 | } 146 | 147 | /// Current ability to communicate with ANY relay. Note that 148 | /// the complete failure to communicate with any relays almost 149 | /// always indicates a problem with the local Internet connection. 150 | /// (However, just because you can reach a single relay doesn't 151 | /// mean that the local connection is in perfect health.) 152 | pub fn any_relay(&self) -> NetworkingAvailabilityResult { 153 | self.any_relay.clone() 154 | } 155 | 156 | /// Non-localized English language status. For diagnostic/debugging 157 | /// purposes only. 158 | pub fn debugging_message(&self) -> &str { 159 | &self.debugging_message 160 | } 161 | } 162 | 163 | impl From for RelayNetworkStatus { 164 | fn from(status: steamworks_sys::SteamRelayNetworkStatus_t) -> Self { 165 | unsafe { 166 | Self { 167 | availability: status.m_eAvail.try_into(), 168 | is_ping_measurement_in_progress: status.m_bPingMeasurementInProgress != 0, 169 | network_config: status.m_eAvailNetworkConfig.try_into(), 170 | any_relay: status.m_eAvailAnyRelay.try_into(), 171 | debugging_message: CStr::from_ptr(status.m_debugMsg.as_ptr()) 172 | .to_str() 173 | .expect("invalid debug string") 174 | .to_owned(), 175 | } 176 | } 177 | } 178 | } 179 | 180 | #[derive(Debug)] 181 | /// The relay network status callback. 182 | pub struct RelayNetworkStatusCallback { 183 | status: RelayNetworkStatus, 184 | } 185 | 186 | impl_callback!(cb: SteamRelayNetworkStatus_t => RelayNetworkStatusCallback { 187 | Self { 188 | status: cb.into(), 189 | } 190 | }); 191 | 192 | #[cfg(test)] 193 | mod tests { 194 | use crate::Client; 195 | use std::time::Duration; 196 | 197 | use serial_test::serial; 198 | 199 | #[test] 200 | #[serial] 201 | fn test_get_networking_status() { 202 | let client = Client::init().unwrap(); 203 | let callback_client = client.clone(); 204 | std::thread::spawn(move || callback_client.run_callbacks()); 205 | 206 | let utils = client.networking_utils(); 207 | let status = utils.detailed_relay_network_status(); 208 | println!( 209 | "status: {:?}, network_config: {:?}, any_relay: {:?}, message: {}", 210 | status.availability(), 211 | status.network_config(), 212 | status.any_relay(), 213 | status.debugging_message() 214 | ); 215 | 216 | utils.init_relay_network_access(); 217 | 218 | let status = utils.detailed_relay_network_status(); 219 | println!( 220 | "status: {:?}, network_config: {:?}, any_relay: {:?}, message: {}", 221 | status.availability(), 222 | status.network_config(), 223 | status.any_relay(), 224 | status.debugging_message() 225 | ); 226 | 227 | std::thread::sleep(Duration::from_millis(500)); 228 | 229 | let status = utils.detailed_relay_network_status(); 230 | println!( 231 | "status: {:?}, network_config: {:?}, any_relay: {:?}, message: {}", 232 | status.availability(), 233 | status.network_config(), 234 | status.any_relay(), 235 | status.debugging_message() 236 | ); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/user_stats/stats.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// Achievement API. 4 | /// 5 | /// Methods require 6 | /// [`request_current_stats()`](../struct.UserStats.html#method.request_current_stats) 7 | /// to have been called and a successful [`UserStatsReceived`](../struct.UserStatsReceived.html) 8 | /// callback processed. 9 | /// 10 | /// # Example 11 | /// 12 | /// ```no_run 13 | /// # use steamworks::*; 14 | /// # let client = steamworks::Client::init().unwrap(); 15 | /// // Unlock the 'WIN_THE_GAME' achievement 16 | /// client.user_stats().achievement("WIN_THE_GAME").set()?; 17 | /// # Err(()) 18 | /// ``` 19 | pub struct AchievementHelper<'parent> { 20 | pub(crate) name: CString, 21 | pub(crate) parent: &'parent UserStats, 22 | } 23 | 24 | impl AchievementHelper<'_> { 25 | /// Gets the unlock status of the Achievement. 26 | /// 27 | /// This call only modifies Steam's in-memory state so it is quite cheap. To send the unlock 28 | /// status to the server and to trigger the Steam overlay notification you must call 29 | /// [`store_stats()`](../struct.UserStats.html#method.store_stats). 30 | /// 31 | /// Fails if this achievement's 'API Name' is unknown, or unsuccessful 32 | /// [`UserStatsReceived`](../struct.UserStatsReceived.html). 33 | pub fn get(&self) -> Result { 34 | unsafe { 35 | let mut achieved = false; 36 | let success = sys::SteamAPI_ISteamUserStats_GetAchievement( 37 | self.parent.user_stats, 38 | self.name.as_ptr(), 39 | &mut achieved, 40 | ); 41 | if success { 42 | Ok(achieved) 43 | } else { 44 | Err(()) 45 | } 46 | } 47 | } 48 | 49 | /// Unlocks an achievement. 50 | /// 51 | /// This call only modifies Steam's in-memory state so it is quite cheap. To send the unlock 52 | /// status to the server and to trigger the Steam overlay notification you must call 53 | /// [`store_stats()`](../struct.UserStats.html#method.store_stats). 54 | /// 55 | /// Fails if this achievement's 'API Name' is unknown, or unsuccessful 56 | /// [`UserStatsReceived`](../struct.UserStatsReceived.html). 57 | pub fn set(&self) -> Result<(), ()> { 58 | let success = unsafe { 59 | sys::SteamAPI_ISteamUserStats_SetAchievement(self.parent.user_stats, self.name.as_ptr()) 60 | }; 61 | if success { 62 | Ok(()) 63 | } else { 64 | Err(()) 65 | } 66 | } 67 | 68 | /// Resets the unlock status of an achievement. 69 | /// 70 | /// This call only modifies Steam's in-memory state so it is quite cheap. To send the unlock 71 | /// status to the server and to trigger the Steam overlay notification you must call 72 | /// [`store_stats()`](../struct.UserStats.html#method.store_stats). 73 | /// 74 | /// Fails if this achievement's 'API Name' is unknown, or unsuccessful 75 | /// [`UserStatsReceived`](../struct.UserStatsReceived.html). 76 | pub fn clear(&self) -> Result<(), ()> { 77 | let success = unsafe { 78 | sys::SteamAPI_ISteamUserStats_ClearAchievement( 79 | self.parent.user_stats, 80 | self.name.as_ptr(), 81 | ) 82 | }; 83 | if success { 84 | Ok(()) 85 | } else { 86 | Err(()) 87 | } 88 | } 89 | 90 | /// Returns the percentage of users who have unlocked the specified achievement. 91 | /// 92 | /// You must have called `request_global_achievement_percentages()` and it needs to return 93 | /// successfully via its callback prior to calling this. 94 | /// 95 | /// *Note: Always returns an error for AppId `480` (Spacewar)! 96 | /// Other AppIds work fine though.* 97 | /// 98 | /// # Example 99 | /// 100 | /// ```no_run 101 | /// # use steamworks::*; 102 | /// # let client = steamworks::Client::init().unwrap(); 103 | /// // Get the current unlock percentage for the 'WIN_THE_GAME' achievement 104 | /// client.user_stats().request_global_achievement_percentages(move|result| { 105 | /// if !result.is_err() { 106 | /// let user_stats = client.user_stats(); 107 | /// let achievement = user_stats.achievement("WIN_THE_GAME"); 108 | /// let ach_percent = achievement.get_achievement_achieved_percent().expect("Failed to get achievement percentage"); 109 | /// 110 | /// println!("{}",ach_percent); 111 | /// } else { 112 | /// println!("Error requesting global achievement percentages"); 113 | /// } 114 | /// }); 115 | /// # Err(()) 116 | /// ``` 117 | pub fn get_achievement_achieved_percent(&self) -> Result { 118 | unsafe { 119 | let mut percent = 0.0; 120 | let success = sys::SteamAPI_ISteamUserStats_GetAchievementAchievedPercent( 121 | self.parent.user_stats, 122 | self.name.as_ptr(), 123 | &mut percent, 124 | ); 125 | if success { 126 | Ok(percent) 127 | } else { 128 | Err(()) 129 | } 130 | } 131 | } 132 | 133 | /// Get general attributes for an achievement. Currently provides: `Name`, `Description`, 134 | /// and `Hidden` status. 135 | /// 136 | /// This receives the value from a dictionary/map keyvalue store, so you must provide one 137 | /// of the following keys: 138 | /// 139 | /// - `"name"` to retrive the localized achievement name in UTF8 140 | /// - `"desc"` to retrive the localized achievement description in UTF8 141 | /// - `"hidden"` for retrieving if an achievement is hidden. Returns `"0"` when not hidden, 142 | /// `"1"` when hidden 143 | /// 144 | /// This localization is provided based on the games language if it's set, otherwise it 145 | /// checks if a localization is available for the users Steam UI Language. If that fails 146 | /// too, then it falls back to english. 147 | /// 148 | /// This function returns the value as a `string` upon success if all of the following 149 | /// conditions are met; otherwise, an empty string: `""`. 150 | /// 151 | /// - `request_current_stats()` has completed and successfully returned its callback. 152 | /// - The specified achievement exists in App Admin on the Steamworks website, and the 153 | /// changes are published. 154 | /// - The specified `pchKey` is valid. 155 | /// 156 | /// # Example 157 | /// 158 | /// ```no_run 159 | /// # use steamworks::*; 160 | /// # let client = steamworks::Client::init().unwrap(); 161 | /// // Get the "description" string for the 'WIN_THE_GAME' achievement 162 | /// client.user_stats().achievement("WIN_THE_GAME").get_achievement_display_attribute("desc").unwrap(); 163 | /// # Err(()) 164 | /// ``` 165 | pub fn get_achievement_display_attribute(&self, key: &str) -> Result<&str, ()> { 166 | unsafe { 167 | let key_c_str = CString::new(key).expect("Failed to create c_str from key parameter"); 168 | 169 | let str = sys::SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute( 170 | self.parent.user_stats, 171 | self.name.as_ptr(), 172 | key_c_str.as_ptr(), 173 | ); 174 | 175 | let c_str = CStr::from_ptr(str); 176 | 177 | match c_str.to_str() { 178 | Ok(result) => Ok(result), 179 | Err(_) => Err(()), 180 | } 181 | } 182 | } 183 | 184 | /// Gets the icon for an achievement. 185 | /// 186 | /// The image is returned as a handle to be used with `ISteamUtils::GetImageRGBA` to get 187 | /// the actual image data.* 188 | /// 189 | /// **Note: This is handled within the function. Returns a `Vec` buffer on success, 190 | /// which can be converted into the image data and saved to disk (e.g. via external RGBA to image crate).* 191 | /// ** Note: This may return None if Steam has not retrieved the icon yet. In that case an `UserAchievementIconFetched` callback will be processed 192 | 193 | pub fn get_achievement_icon(&self) -> Option> { 194 | Some(self.internal_get_achievement_icon(true)?.0) 195 | } 196 | 197 | fn internal_get_achievement_icon(&self, avoid_big_icons: bool) -> Option<(Vec, u32, u32)> { 198 | unsafe { 199 | let utils: *mut sys::ISteamUtils = sys::SteamAPI_SteamUtils_v010(); 200 | let img = sys::SteamAPI_ISteamUserStats_GetAchievementIcon( 201 | self.parent.user_stats, 202 | self.name.as_ptr(), 203 | ); 204 | if img == 0 { 205 | return None; 206 | } 207 | let mut width = 0; 208 | let mut height = 0; 209 | if !sys::SteamAPI_ISteamUtils_GetImageSize(utils, img, &mut width, &mut height) { 210 | return None; 211 | } 212 | if avoid_big_icons && (width != 64 || height != 64) { 213 | return None; 214 | } 215 | let mut dest = vec![0; (width * height * 4).try_into().unwrap()]; 216 | if !sys::SteamAPI_ISteamUtils_GetImageRGBA( 217 | utils, 218 | img, 219 | dest.as_mut_ptr(), 220 | (width * height * 4).try_into().unwrap(), 221 | ) { 222 | return None; 223 | } 224 | Some((dest, width, height)) 225 | } 226 | } 227 | 228 | /// Gets the icon for an achievement. 229 | /// 230 | /// The image is returned as a handle to be used with `ISteamUtils::GetImageRGBA` to get 231 | /// the actual image data.* 232 | /// 233 | /// **Note: This is handled within the function. Returns a `ImageBuffer::, Vec>` from the image crate on success** 234 | /// ** Note: This may return None if Steam has not retrieved the icon yet. In that case an `UserAchievementIconFetched` callback will be processed 235 | #[cfg(feature = "image")] 236 | pub fn get_achievement_icon_v2(&self) -> Option, Vec>> { 237 | let (vec, width, height) = self.internal_get_achievement_icon(false)?; 238 | let img = image::ImageBuffer::, Vec>::from_vec(width, height, vec)?; 239 | return Some(img); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/isteamapps.h: -------------------------------------------------------------------------------- 1 | //====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= 2 | // 3 | // Purpose: interface to app data in Steam 4 | // 5 | //============================================================================= 6 | 7 | #ifndef ISTEAMAPPS_H 8 | #define ISTEAMAPPS_H 9 | #ifdef _WIN32 10 | #pragma once 11 | #endif 12 | 13 | #include "steam_api_common.h" 14 | 15 | const int k_cubAppProofOfPurchaseKeyMax = 240; // max supported length of a legacy cd key 16 | 17 | 18 | //----------------------------------------------------------------------------- 19 | // Purpose: interface to app data 20 | //----------------------------------------------------------------------------- 21 | class ISteamApps 22 | { 23 | public: 24 | virtual bool BIsSubscribed() = 0; 25 | virtual bool BIsLowViolence() = 0; 26 | virtual bool BIsCybercafe() = 0; 27 | virtual bool BIsVACBanned() = 0; 28 | virtual const char *GetCurrentGameLanguage() = 0; 29 | virtual const char *GetAvailableGameLanguages() = 0; 30 | 31 | // only use this member if you need to check ownership of another game related to yours, a demo for example 32 | virtual bool BIsSubscribedApp( AppId_t appID ) = 0; 33 | 34 | // Takes AppID of DLC and checks if the user owns the DLC & if the DLC is installed 35 | virtual bool BIsDlcInstalled( AppId_t appID ) = 0; 36 | 37 | // returns the Unix time of the purchase of the app 38 | virtual uint32 GetEarliestPurchaseUnixTime( AppId_t nAppID ) = 0; 39 | 40 | // Checks if the user is subscribed to the current app through a free weekend 41 | // This function will return false for users who have a retail or other type of license 42 | // Before using, please ask your Valve technical contact how to package and secure your free weekend 43 | virtual bool BIsSubscribedFromFreeWeekend() = 0; 44 | 45 | // Returns the number of DLC pieces for the running app 46 | virtual int GetDLCCount() = 0; 47 | 48 | // Returns metadata for DLC by index, of range [0, GetDLCCount()] 49 | virtual bool BGetDLCDataByIndex( int iDLC, AppId_t *pAppID, bool *pbAvailable, char *pchName, int cchNameBufferSize ) = 0; 50 | 51 | // Install/Uninstall control for optional DLC 52 | virtual void InstallDLC( AppId_t nAppID ) = 0; 53 | virtual void UninstallDLC( AppId_t nAppID ) = 0; 54 | 55 | // Request legacy cd-key for yourself or owned DLC. If you are interested in this 56 | // data then make sure you provide us with a list of valid keys to be distributed 57 | // to users when they purchase the game, before the game ships. 58 | // You'll receive an AppProofOfPurchaseKeyResponse_t callback when 59 | // the key is available (which may be immediately). 60 | virtual void RequestAppProofOfPurchaseKey( AppId_t nAppID ) = 0; 61 | 62 | virtual bool GetCurrentBetaName( char *pchName, int cchNameBufferSize ) = 0; // returns current beta branch name, 'public' is the default branch 63 | virtual bool MarkContentCorrupt( bool bMissingFilesOnly ) = 0; // signal Steam that game files seems corrupt or missing 64 | virtual uint32 GetInstalledDepots( AppId_t appID, DepotId_t *pvecDepots, uint32 cMaxDepots ) = 0; // return installed depots in mount order 65 | 66 | // returns current app install folder for AppID, returns folder name length 67 | virtual uint32 GetAppInstallDir( AppId_t appID, char *pchFolder, uint32 cchFolderBufferSize ) = 0; 68 | virtual bool BIsAppInstalled( AppId_t appID ) = 0; // returns true if that app is installed (not necessarily owned) 69 | 70 | // returns the SteamID of the original owner. If this CSteamID is different from ISteamUser::GetSteamID(), 71 | // the user has a temporary license borrowed via Family Sharing 72 | virtual CSteamID GetAppOwner() = 0; 73 | 74 | // Returns the associated launch param if the game is run via steam://run///?param1=value1¶m2=value2¶m3=value3 etc. 75 | // Parameter names starting with the character '@' are reserved for internal use and will always return and empty string. 76 | // Parameter names starting with an underscore '_' are reserved for steam features -- they can be queried by the game, 77 | // but it is advised that you not param names beginning with an underscore for your own features. 78 | // Check for new launch parameters on callback NewUrlLaunchParameters_t 79 | virtual const char *GetLaunchQueryParam( const char *pchKey ) = 0; 80 | 81 | // get download progress for optional DLC 82 | virtual bool GetDlcDownloadProgress( AppId_t nAppID, uint64 *punBytesDownloaded, uint64 *punBytesTotal ) = 0; 83 | 84 | // return the buildid of this app, may change at any time based on backend updates to the game 85 | virtual int GetAppBuildId() = 0; 86 | 87 | // Request all proof of purchase keys for the calling appid and associated DLC. 88 | // A series of AppProofOfPurchaseKeyResponse_t callbacks will be sent with 89 | // appropriate appid values, ending with a final callback where the m_nAppId 90 | // member is k_uAppIdInvalid (zero). 91 | virtual void RequestAllProofOfPurchaseKeys() = 0; 92 | 93 | STEAM_CALL_RESULT( FileDetailsResult_t ) 94 | virtual SteamAPICall_t GetFileDetails( const char* pszFileName ) = 0; 95 | 96 | // Get command line if game was launched via Steam URL, e.g. steam://run////. 97 | // This method of passing a connect string (used when joining via rich presence, accepting an 98 | // invite, etc) is preferable to passing the connect string on the operating system command 99 | // line, which is a security risk. In order for rich presence joins to go through this 100 | // path and not be placed on the OS command line, you must set a value in your app's 101 | // configuration on Steam. Ask Valve for help with this. 102 | // 103 | // If game was already running and launched again, the NewUrlLaunchParameters_t will be fired. 104 | virtual int GetLaunchCommandLine( char *pszCommandLine, int cubCommandLine ) = 0; 105 | 106 | // Check if user borrowed this game via Family Sharing, If true, call GetAppOwner() to get the lender SteamID 107 | virtual bool BIsSubscribedFromFamilySharing() = 0; 108 | 109 | // check if game is a timed trial with limited playtime 110 | virtual bool BIsTimedTrial( uint32* punSecondsAllowed, uint32* punSecondsPlayed ) = 0; 111 | 112 | // set current DLC AppID being played (or 0 if none). Allows Steam to track usage of major DLC extensions 113 | virtual bool SetDlcContext( AppId_t nAppID ) = 0; 114 | 115 | // returns total number of known app branches (including default "public" branch ). nAvailable is number of available betas 116 | virtual int GetNumBetas( int *pnAvailable, int *pnPrivate ) = 0; // 117 | 118 | // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) 119 | virtual bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) = 0; // iterate through 120 | 121 | // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch 122 | virtual bool SetActiveBeta( const char *pchBetaName ) = 0; 123 | }; 124 | 125 | #define STEAMAPPS_INTERFACE_VERSION "STEAMAPPS_INTERFACE_VERSION008" 126 | 127 | // Global interface accessor 128 | inline ISteamApps *SteamApps(); 129 | STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamApps *, SteamApps, STEAMAPPS_INTERFACE_VERSION ); 130 | 131 | // callbacks 132 | #if defined( VALVE_CALLBACK_PACK_SMALL ) 133 | #pragma pack( push, 4 ) 134 | #elif defined( VALVE_CALLBACK_PACK_LARGE ) 135 | #pragma pack( push, 8 ) 136 | #else 137 | #error steam_api_common.h should define VALVE_CALLBACK_PACK_xxx 138 | #endif 139 | //----------------------------------------------------------------------------- 140 | // Purpose: posted after the user gains ownership of DLC & that DLC is installed 141 | //----------------------------------------------------------------------------- 142 | struct DlcInstalled_t 143 | { 144 | enum { k_iCallback = k_iSteamAppsCallbacks + 5 }; 145 | AppId_t m_nAppID; // AppID of the DLC 146 | }; 147 | 148 | 149 | //--------------------------------------------------------------------------------- 150 | // Purpose: posted after the user gains executes a Steam URL with command line or query parameters 151 | // such as steam://run///-commandline/?param1=value1¶m2=value2¶m3=value3 etc 152 | // while the game is already running. The new params can be queried 153 | // with GetLaunchQueryParam and GetLaunchCommandLine 154 | //--------------------------------------------------------------------------------- 155 | struct NewUrlLaunchParameters_t 156 | { 157 | enum { k_iCallback = k_iSteamAppsCallbacks + 14 }; 158 | }; 159 | 160 | 161 | //----------------------------------------------------------------------------- 162 | // Purpose: response to RequestAppProofOfPurchaseKey/RequestAllProofOfPurchaseKeys 163 | // for supporting third-party CD keys, or other proof-of-purchase systems. 164 | //----------------------------------------------------------------------------- 165 | struct AppProofOfPurchaseKeyResponse_t 166 | { 167 | enum { k_iCallback = k_iSteamAppsCallbacks + 21 }; 168 | EResult m_eResult; 169 | uint32 m_nAppID; 170 | uint32 m_cchKeyLength; 171 | char m_rgchKey[k_cubAppProofOfPurchaseKeyMax]; 172 | }; 173 | 174 | 175 | //----------------------------------------------------------------------------- 176 | // Purpose: response to GetFileDetails 177 | //----------------------------------------------------------------------------- 178 | struct FileDetailsResult_t 179 | { 180 | enum { k_iCallback = k_iSteamAppsCallbacks + 23 }; 181 | EResult m_eResult; 182 | uint64 m_ulFileSize; // original file size in bytes 183 | uint8 m_FileSHA[20]; // original file SHA1 hash 184 | uint32 m_unFlags; // 185 | }; 186 | 187 | 188 | //----------------------------------------------------------------------------- 189 | // Purpose: called for games in Timed Trial mode 190 | //----------------------------------------------------------------------------- 191 | struct TimedTrialStatus_t 192 | { 193 | enum { k_iCallback = k_iSteamAppsCallbacks + 30 }; 194 | AppId_t m_unAppID; // appID 195 | bool m_bIsOffline; // if true, time allowed / played refers to offline time, not total time 196 | uint32 m_unSecondsAllowed; // how many seconds the app can be played in total 197 | uint32 m_unSecondsPlayed; // how many seconds the app was already played 198 | }; 199 | 200 | #pragma pack( pop ) 201 | #endif // ISTEAMAPPS_H 202 | -------------------------------------------------------------------------------- /examples/chat-example/src/main.rs: -------------------------------------------------------------------------------- 1 | use clipboard::{ClipboardContext, ClipboardProvider}; 2 | use macroquad::prelude::*; 3 | use macroquad::ui::*; 4 | use std::sync::mpsc; 5 | use steamworks::*; 6 | 7 | //MAYBE IT'S NOT GOOD GAME ARCHITECTURE 8 | struct GameState { 9 | state: Box, 10 | } 11 | 12 | enum States { 13 | Menu(MenuState), 14 | Chat(ChatState), 15 | } 16 | 17 | struct MenuState { 18 | lobby_input: String, 19 | } 20 | 21 | struct ChatState { 22 | own_id: SteamId, 23 | current_lobby: LobbyId, 24 | is_host: bool, 25 | host_id: SteamId, 26 | peers: Vec, 27 | message: String, 28 | } 29 | 30 | fn window_conf() -> Conf { 31 | Conf { 32 | window_title: "Test".parse().unwrap(), 33 | window_resizable: false, 34 | window_width: 800, 35 | window_height: 600, 36 | ..Default::default() 37 | } 38 | } 39 | 40 | #[macroquad::main(window_conf)] 41 | async fn main() { 42 | let client = Client::init().unwrap(); 43 | 44 | let matchmaking = client.matchmaking(); 45 | let networking = client.networking(); 46 | 47 | let mut state = GameState { 48 | state: Box::new(States::Menu(MenuState { 49 | lobby_input: String::new(), 50 | })), 51 | }; 52 | 53 | //For getting values from callback 54 | let (sender_create_lobby, receiver_create_lobby) = mpsc::channel(); 55 | let (sender_join_lobby, receiver_join_lobby) = mpsc::channel(); 56 | let (sender_accept, receiver_accept) = mpsc::channel(); 57 | 58 | //YOU MUST KEEP CALLBACK IN VARIABLE OTHERWISE CALLBACK WILL NOT WORK 59 | let _request_callback = client.register_callback(move |request: P2PSessionRequest| { 60 | println!("ACCEPTED PEER"); 61 | sender_accept.send(request.remote).unwrap(); 62 | }); 63 | 64 | //Should be in ChatState :) 65 | let mut messages: Vec = vec![]; 66 | 67 | loop { 68 | client.run_callbacks(); 69 | clear_background(BLACK); 70 | 71 | let local_sender_create_lobby = sender_create_lobby.clone(); 72 | let local_sender_join_lobby = sender_join_lobby.clone(); 73 | 74 | // Macroquad doesn't show UI if a Group is the same exact size as the screen 75 | let window = vec2(screen_width() - 0.1, screen_height() - 0.1); 76 | 77 | match state.state.as_mut() { 78 | States::Menu(menu_state) => { 79 | widgets::Group::new(hash!(), window).ui(&mut *root_ui(), |ui| { 80 | //Creating lobby by button 81 | if ui.button(vec2(20.0, 40.0), "Create Lobby") { 82 | matchmaking.create_lobby(LobbyType::FriendsOnly, 4, move |lobby| { 83 | match lobby { 84 | Ok(lobby) => { 85 | local_sender_create_lobby.send(lobby).unwrap(); 86 | } 87 | Err(_) => {} 88 | }; 89 | }); 90 | } 91 | 92 | //Try to join in lobby with id from InputField 93 | if ui.button(vec2(120.0, 40.0), "Connect") { 94 | let lobby_id: Result = menu_state.lobby_input.parse(); 95 | match lobby_id { 96 | Ok(id) => { 97 | matchmaking.join_lobby(LobbyId::from_raw(id), move |result| { 98 | if let Ok(lobby) = result { 99 | local_sender_join_lobby.send(lobby).unwrap(); 100 | } 101 | }); 102 | } 103 | Err(_) => {} 104 | } 105 | } 106 | 107 | widgets::Group::new(hash!(), vec2(200.0, 25.0)) 108 | .position(vec2(20.0, 60.0)) 109 | .ui(ui, |ui| { 110 | ui.input_text(hash!(), "", &mut menu_state.lobby_input); 111 | }); 112 | }); 113 | } 114 | States::Chat(lobby_state) => { 115 | widgets::Group::new(hash!(), window).ui(&mut *root_ui(), |ui| { 116 | draw_text_ex( 117 | &format!("LobbyID: {}", lobby_state.current_lobby.raw()), 118 | 20.0, 119 | 20.0, 120 | TextParams::default(), 121 | ); 122 | 123 | for (id, message) in messages.iter().enumerate() { 124 | draw_text_ex( 125 | message, 126 | 20.0, 127 | 40.0 + 20.0 * (id as f32), 128 | TextParams::default(), 129 | ); 130 | } 131 | 132 | widgets::Group::new(hash!(), vec2(250.0, 25.0)) 133 | .position(vec2(20.0, 570.0)) 134 | .ui(ui, |ui| { 135 | ui.input_text(hash!(), "", &mut lobby_state.message); 136 | }); 137 | 138 | //Little Client-Server logic 139 | //Host is server. Host sends message to all players 140 | //Clients send message only to host 141 | if ui.button(vec2(300.0, 570.0), "Send") { 142 | let message = 143 | format!("{}: {}", client.friends().name(), lobby_state.message); 144 | if lobby_state.is_host { 145 | for peer in lobby_state.peers.iter() { 146 | if peer.raw() != lobby_state.own_id.raw() { 147 | println!("SENT {} TO {}", message, peer.raw()); 148 | networking.send_p2p_packet( 149 | *peer, 150 | SendType::Reliable, 151 | message.as_bytes(), 152 | ); 153 | } 154 | } 155 | } else { 156 | let result = networking.send_p2p_packet( 157 | lobby_state.host_id, 158 | SendType::Reliable, 159 | message.as_bytes(), 160 | ); 161 | println!( 162 | "SENT {} TO {}. RESULT: {}", 163 | message, 164 | lobby_state.host_id.raw(), 165 | result 166 | ); 167 | } 168 | messages.push(message); 169 | lobby_state.message = String::new(); 170 | } 171 | }); 172 | 173 | while let Some(size) = networking.is_p2p_packet_available() { 174 | let mut empty_array = vec![0; size]; 175 | let mut buffer = empty_array.as_mut_slice(); 176 | if let Some((sender, _)) = networking.read_p2p_packet(&mut buffer) { 177 | //Host gets message from one of the players and sends this message to other players 178 | if lobby_state.is_host { 179 | for peer in lobby_state.peers.iter() { 180 | if peer.raw() != lobby_state.own_id.raw() 181 | && peer.raw() != sender.raw() 182 | { 183 | networking.send_p2p_packet( 184 | *peer, 185 | SendType::Reliable, 186 | format!( 187 | "{}: {}", 188 | client.friends().name(), 189 | lobby_state.message 190 | ) 191 | .as_bytes(), 192 | ); 193 | } 194 | } 195 | } 196 | 197 | if let Ok(message) = String::from_utf8(Vec::from(buffer)) { 198 | println!("GOT MESSAGE: {}", message); 199 | messages.push(message); 200 | } 201 | } 202 | } 203 | 204 | if let Ok(user) = receiver_accept.try_recv() { 205 | println!("GET REQUEST FROM {}", user.raw()); 206 | if lobby_state.is_host { 207 | lobby_state.peers.push(user); 208 | } 209 | networking.accept_p2p_session(user); 210 | } 211 | } 212 | } 213 | 214 | if let Ok(lobby) = receiver_create_lobby.try_recv() { 215 | println!("CREATED LOBBY WITH ID: {}", lobby.raw()); 216 | let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap(); 217 | let _ = ctx.set_contents(lobby.raw().to_string()); 218 | state.state = Box::new(States::Chat(ChatState { 219 | own_id: client.user().steam_id(), 220 | current_lobby: lobby, 221 | is_host: true, 222 | host_id: client.user().steam_id(), 223 | peers: vec![client.user().steam_id()], 224 | message: String::new(), 225 | })); 226 | } 227 | 228 | if let Ok(lobby) = receiver_join_lobby.try_recv() { 229 | println!("JOINED TO LOBBY WITH ID: {}", lobby.raw()); 230 | let host_id = matchmaking.lobby_owner(lobby); 231 | state.state = Box::new(States::Chat(ChatState { 232 | own_id: client.user().steam_id(), 233 | current_lobby: lobby, 234 | is_host: false, 235 | host_id, 236 | peers: vec![], 237 | message: String::new(), 238 | })); 239 | 240 | //When you connected to lobby you have to send a "ping" message to host 241 | //After that host will add you into peer list 242 | networking.send_p2p_packet( 243 | host_id, 244 | SendType::Reliable, 245 | format!("{} JOINED", client.friends().name()).as_bytes(), 246 | ); 247 | } 248 | 249 | next_frame().await 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use sys::InputHandle_t; 2 | 3 | use super::*; 4 | 5 | /// Access to the steam input interface 6 | pub struct Input { 7 | pub(crate) input: *mut sys::ISteamInput, 8 | pub(crate) _inner: Arc, 9 | } 10 | 11 | pub enum InputType { 12 | Unknown, 13 | SteamController, 14 | XBox360Controller, 15 | XBoxOneController, 16 | GenericGamepad, 17 | PS4Controller, 18 | AppleMFiController, 19 | AndroidController, 20 | SwitchJoyConPair, 21 | SwitchJoyConSingle, 22 | SwitchProController, 23 | MobileTouch, 24 | PS3Controller, 25 | PS5Controller, 26 | SteamDeckController, 27 | } 28 | 29 | impl Input { 30 | /// Init must be called when starting use of this interface. 31 | /// if explicitly_call_run_frame is called then you will need to manually call RunFrame 32 | /// each frame, otherwise Steam Input will updated when SteamAPI_RunCallbacks() is called 33 | pub fn init(&self, explicitly_call_run_frame: bool) -> bool { 34 | unsafe { sys::SteamAPI_ISteamInput_Init(self.input, explicitly_call_run_frame) } 35 | } 36 | 37 | /// Synchronize API state with the latest Steam Input action data available. This 38 | /// is performed automatically by SteamAPI_RunCallbacks, but for the absolute lowest 39 | /// possible latency, you call this directly before reading controller state. 40 | /// Note: This must be called from somewhere before GetConnectedControllers will 41 | /// return any handles 42 | pub fn run_frame(&self) { 43 | unsafe { sys::SteamAPI_ISteamInput_RunFrame(self.input, false) } 44 | } 45 | 46 | /// Returns a list of the currently connected controllers 47 | pub fn get_connected_controllers(&self) -> Vec { 48 | let mut handles = vec![0_u64; sys::STEAM_INPUT_MAX_COUNT as usize]; 49 | let quantity = self.get_connected_controllers_slice(&mut handles); 50 | handles.shrink_to(quantity); 51 | handles 52 | } 53 | 54 | /// Returns a list of the currently connected controllers without allocating, and the count 55 | pub fn get_connected_controllers_slice( 56 | &self, 57 | mut controllers: impl AsMut<[InputHandle_t]>, 58 | ) -> usize { 59 | let handles = controllers.as_mut(); 60 | assert!(handles.len() >= sys::STEAM_INPUT_MAX_COUNT as usize); 61 | unsafe { 62 | return sys::SteamAPI_ISteamInput_GetConnectedControllers( 63 | self.input, 64 | handles.as_mut_ptr(), 65 | ) as usize; 66 | } 67 | } 68 | 69 | /// Allows to load a specific Action Manifest File localy 70 | pub fn set_input_action_manifest_file_path(&self, path: &str) -> bool { 71 | let path = CString::new(path).unwrap(); 72 | unsafe { 73 | sys::SteamAPI_ISteamInput_SetInputActionManifestFilePath(self.input, path.as_ptr()) 74 | } 75 | } 76 | 77 | /// Returns the associated ControllerActionSet handle for the specified controller, 78 | pub fn get_action_set_handle(&self, action_set_name: &str) -> sys::InputActionSetHandle_t { 79 | let name = CString::new(action_set_name).unwrap(); 80 | unsafe { sys::SteamAPI_ISteamInput_GetActionSetHandle(self.input, name.as_ptr()) } 81 | } 82 | 83 | /// Returns the input type for a controler 84 | pub fn get_input_type_for_handle(&self, input_handle: sys::InputHandle_t) -> InputType { 85 | let input_type: sys::ESteamInputType = 86 | unsafe { sys::SteamAPI_ISteamInput_GetInputTypeForHandle(self.input, input_handle) }; 87 | 88 | match input_type { 89 | sys::ESteamInputType::k_ESteamInputType_SteamController => InputType::SteamController, 90 | sys::ESteamInputType::k_ESteamInputType_GenericGamepad => InputType::GenericGamepad, 91 | sys::ESteamInputType::k_ESteamInputType_PS4Controller => InputType::PS4Controller, 92 | sys::ESteamInputType::k_ESteamInputType_SwitchJoyConPair => InputType::SwitchJoyConPair, 93 | sys::ESteamInputType::k_ESteamInputType_MobileTouch => InputType::MobileTouch, 94 | sys::ESteamInputType::k_ESteamInputType_PS3Controller => InputType::PS3Controller, 95 | sys::ESteamInputType::k_ESteamInputType_PS5Controller => InputType::PS5Controller, 96 | sys::ESteamInputType::k_ESteamInputType_XBox360Controller => { 97 | InputType::XBox360Controller 98 | } 99 | sys::ESteamInputType::k_ESteamInputType_XBoxOneController => { 100 | InputType::XBoxOneController 101 | } 102 | sys::ESteamInputType::k_ESteamInputType_AppleMFiController => { 103 | InputType::AppleMFiController 104 | } 105 | sys::ESteamInputType::k_ESteamInputType_AndroidController => { 106 | InputType::AndroidController 107 | } 108 | sys::ESteamInputType::k_ESteamInputType_SwitchJoyConSingle => { 109 | InputType::SwitchJoyConSingle 110 | } 111 | sys::ESteamInputType::k_ESteamInputType_SwitchProController => { 112 | InputType::SwitchProController 113 | } 114 | sys::ESteamInputType::k_ESteamInputType_SteamDeckController => { 115 | InputType::SteamDeckController 116 | } 117 | _ => InputType::Unknown, 118 | } 119 | } 120 | 121 | /// Returns the glyph for an input action 122 | pub fn get_glyph_for_action_origin(&self, action_origin: sys::EInputActionOrigin) -> String { 123 | unsafe { 124 | let glyph_path = 125 | sys::SteamAPI_ISteamInput_GetGlyphForActionOrigin_Legacy(self.input, action_origin); 126 | let glyph_path = CStr::from_ptr(glyph_path); 127 | glyph_path.to_string_lossy().into_owned() 128 | } 129 | } 130 | 131 | /// Returns the name of an input action 132 | pub fn get_string_for_action_origin(&self, action_origin: sys::EInputActionOrigin) -> String { 133 | unsafe { 134 | let name_path = 135 | sys::SteamAPI_ISteamInput_GetStringForActionOrigin(self.input, action_origin); 136 | let name_path = CStr::from_ptr(name_path); 137 | name_path.to_string_lossy().into_owned() 138 | } 139 | } 140 | 141 | /// Reconfigure the controller to use the specified action set 142 | /// This is cheap, and can be safely called repeatedly. 143 | pub fn activate_action_set_handle( 144 | &self, 145 | input_handle: sys::InputHandle_t, 146 | action_set_handle: sys::InputActionSetHandle_t, 147 | ) { 148 | unsafe { 149 | sys::SteamAPI_ISteamInput_ActivateActionSet(self.input, input_handle, action_set_handle) 150 | } 151 | } 152 | 153 | /// Get the handle of the specified Digital action. 154 | pub fn get_digital_action_handle(&self, action_name: &str) -> sys::InputDigitalActionHandle_t { 155 | let name = CString::new(action_name).unwrap(); 156 | unsafe { sys::SteamAPI_ISteamInput_GetDigitalActionHandle(self.input, name.as_ptr()) } 157 | } 158 | 159 | /// Get the handle of the specified Analog action. 160 | pub fn get_analog_action_handle(&self, action_name: &str) -> sys::InputAnalogActionHandle_t { 161 | let name = CString::new(action_name).unwrap(); 162 | unsafe { sys::SteamAPI_ISteamInput_GetAnalogActionHandle(self.input, name.as_ptr()) } 163 | } 164 | 165 | /// Returns the current state of the supplied digital game action. 166 | pub fn get_digital_action_data( 167 | &self, 168 | input_handle: sys::InputHandle_t, 169 | action_handle: sys::InputDigitalActionHandle_t, 170 | ) -> sys::InputDigitalActionData_t { 171 | unsafe { 172 | sys::SteamAPI_ISteamInput_GetDigitalActionData(self.input, input_handle, action_handle) 173 | } 174 | } 175 | 176 | /// Returns the current state of the supplied analog game action. 177 | pub fn get_analog_action_data( 178 | &self, 179 | input_handle: sys::InputHandle_t, 180 | action_handle: sys::InputAnalogActionHandle_t, 181 | ) -> sys::InputAnalogActionData_t { 182 | unsafe { 183 | sys::SteamAPI_ISteamInput_GetAnalogActionData(self.input, input_handle, action_handle) 184 | } 185 | } 186 | 187 | /// Get the origin(s) for a digital action within an action set. 188 | pub fn get_digital_action_origins( 189 | &self, 190 | input_handle: sys::InputHandle_t, 191 | action_set_handle: sys::InputActionSetHandle_t, 192 | digital_action_handle: sys::InputDigitalActionHandle_t, 193 | ) -> Vec { 194 | unsafe { 195 | let mut origins = Vec::with_capacity(sys::STEAM_INPUT_MAX_ORIGINS as usize); 196 | let len = sys::SteamAPI_ISteamInput_GetDigitalActionOrigins( 197 | self.input, 198 | input_handle, 199 | action_set_handle, 200 | digital_action_handle, 201 | origins.as_mut_ptr(), 202 | ); 203 | origins.set_len(len as usize); 204 | origins 205 | } 206 | } 207 | 208 | /// Get the origin(s) for an analog action within an action set. 209 | pub fn get_analog_action_origins( 210 | &self, 211 | input_handle: sys::InputHandle_t, 212 | action_set_handle: sys::InputActionSetHandle_t, 213 | analog_action_handle: sys::InputAnalogActionHandle_t, 214 | ) -> Vec { 215 | unsafe { 216 | let mut origins = Vec::with_capacity(sys::STEAM_INPUT_MAX_ORIGINS as usize); 217 | let len = sys::SteamAPI_ISteamInput_GetAnalogActionOrigins( 218 | self.input, 219 | input_handle, 220 | action_set_handle, 221 | analog_action_handle, 222 | origins.as_mut_ptr(), 223 | ); 224 | origins.set_len(len as usize); 225 | origins 226 | } 227 | } 228 | 229 | pub fn get_motion_data(&self, input_handle: sys::InputHandle_t) -> sys::InputMotionData_t { 230 | unsafe { sys::SteamAPI_ISteamInput_GetMotionData(self.input, input_handle) } 231 | } 232 | 233 | /// Invokes the Steam overlay and brings up the binding screen. 234 | /// Returns true for success, false if overlay is disabled/unavailable. 235 | /// If the player is using Big Picture Mode the configuration will open in 236 | /// the overlay. In desktop mode a popup window version of Big Picture will 237 | /// be created and open the configuration. 238 | pub fn show_binding_panel(&self, input_handle: sys::InputHandle_t) -> bool { 239 | unsafe { sys::SteamAPI_ISteamInput_ShowBindingPanel(self.input, input_handle) } 240 | } 241 | 242 | /// Shutdown must be called when ending use of this interface. 243 | pub fn shutdown(&self) { 244 | unsafe { 245 | sys::SteamAPI_ISteamInput_Shutdown(self.input); 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /steamworks-sys/lib/steam/public/steam/steam_api_common.h: -------------------------------------------------------------------------------- 1 | //====== Copyright Valve Corporation, All rights reserved. ==================== 2 | // 3 | // Steamworks SDK minimal include 4 | // 5 | // Defines the minimal set of things we need to use any single interface 6 | // or register for any callback. 7 | // 8 | //============================================================================= 9 | 10 | #ifndef STEAM_API_COMMON_H 11 | #define STEAM_API_COMMON_H 12 | 13 | #include "steamtypes.h" 14 | #include "steamclientpublic.h" 15 | 16 | // S_API defines the linkage and calling conventions for steam_api.dll exports 17 | #if defined( _WIN32 ) && !defined( _X360 ) 18 | #if defined( STEAM_API_EXPORTS ) 19 | #define S_API extern "C" __declspec( dllexport ) 20 | #elif defined( STEAM_API_NODLL ) 21 | #define S_API extern "C" 22 | #else 23 | #define S_API extern "C" __declspec( dllimport ) 24 | #endif // STEAM_API_EXPORTS 25 | #elif defined( __GNUC__ ) 26 | #if defined( STEAM_API_EXPORTS ) 27 | #define S_API extern "C" __attribute__ ((visibility("default"))) 28 | #else 29 | #define S_API extern "C" 30 | #endif // STEAM_API_EXPORTS 31 | #else // !WIN32 32 | #if defined( STEAM_API_EXPORTS ) 33 | #define S_API extern "C" 34 | #else 35 | #define S_API extern "C" 36 | #endif // STEAM_API_EXPORTS 37 | #endif 38 | 39 | #if defined( __cplusplus ) && ( __cplusplus >= 201103L ) 40 | #define S_OVERRIDE override 41 | #else 42 | #define S_OVERRIDE 43 | #endif 44 | 45 | #if ( defined(STEAM_API_EXPORTS) || defined(STEAM_API_NODLL) ) && !defined(API_GEN) 46 | #define STEAM_PRIVATE_API( ... ) __VA_ARGS__ 47 | #elif defined(STEAM_API_EXPORTS) && defined(API_GEN) 48 | #define STEAM_PRIVATE_API( ... ) 49 | #else 50 | #define STEAM_PRIVATE_API( ... ) protected: __VA_ARGS__ public: 51 | #endif 52 | 53 | // handle to a communication pipe to the Steam client 54 | typedef int32 HSteamPipe; 55 | // handle to single instance of a steam user 56 | typedef int32 HSteamUser; 57 | 58 | // A fixed size buffer to receive an error message that is returned by some API 59 | // calls. 60 | const int k_cchMaxSteamErrMsg = 1024; 61 | typedef char SteamErrMsg[ k_cchMaxSteamErrMsg ]; 62 | 63 | // #define away __cdecl on posix. 64 | // This is really, really bad. We're sorry. But it's been this way for 65 | // a long time now and it's scary to change it, as there may be others that 66 | // depend on it. 67 | #ifndef _WIN32 68 | #define __cdecl 69 | #endif 70 | 71 | // function prototype 72 | extern "C" typedef void ( S_CALLTYPE *SteamAPIWarningMessageHook_t )( int, const char * ); 73 | extern "C" typedef uint32 ( S_CALLTYPE *SteamAPI_CheckCallbackRegistered_t )( int iCallbackNum ); 74 | #if defined( __SNC__ ) 75 | #pragma diag_suppress=1700 // warning 1700: class "%s" has virtual functions but non-virtual destructor 76 | #endif 77 | 78 | //----------------------------------------------------------------------------------------------------------------------------------------------------------// 79 | // steam callback and call-result helpers 80 | // 81 | // The following macros and classes are used to register your application for 82 | // callbacks and call-results, which are delivered in a predictable manner. 83 | // 84 | // STEAM_CALLBACK macros are meant for use inside of a C++ class definition. 85 | // They map a Steam notification callback directly to a class member function 86 | // which is automatically prototyped as "void func( callback_type *pParam )". 87 | // 88 | // CCallResult is used with specific Steam APIs that return "result handles". 89 | // The handle can be passed to a CCallResult object's Set function, along with 90 | // an object pointer and member-function pointer. The member function will 91 | // be executed once the results of the Steam API call are available. 92 | // 93 | // CCallback and CCallbackManual classes can be used instead of STEAM_CALLBACK 94 | // macros if you require finer control over registration and unregistration. 95 | // 96 | // Callbacks and call-results are queued automatically and are only 97 | // delivered/executed when your application calls SteamAPI_RunCallbacks(). 98 | // 99 | // Note that there is an alternative, lower level callback dispatch mechanism. 100 | // See SteamAPI_ManualDispatch_Init 101 | //----------------------------------------------------------------------------------------------------------------------------------------------------------// 102 | 103 | // Dispatch all queued Steamworks callbacks. 104 | // 105 | // This is safe to call from multiple threads simultaneously, 106 | // but if you choose to do this, callback code could be executed on any thread. 107 | // One alternative is to call SteamAPI_RunCallbacks from the main thread only, 108 | // and call SteamAPI_ReleaseCurrentThreadMemory regularly on other threads. 109 | S_API void S_CALLTYPE SteamAPI_RunCallbacks(); 110 | 111 | // Declares a callback member function plus a helper member variable which 112 | // registers the callback on object creation and unregisters on destruction. 113 | // The optional fourth 'var' param exists only for backwards-compatibility 114 | // and can be ignored. 115 | #define STEAM_CALLBACK( thisclass, func, .../*callback_type, [deprecated] var*/ ) \ 116 | _STEAM_CALLBACK_SELECT( ( __VA_ARGS__, 4, 3 ), ( /**/, thisclass, func, __VA_ARGS__ ) ) 117 | 118 | // Declares a callback function and a named CCallbackManual variable which 119 | // has Register and Unregister functions instead of automatic registration. 120 | #define STEAM_CALLBACK_MANUAL( thisclass, func, callback_type, var ) \ 121 | CCallbackManual< thisclass, callback_type > var; void func( callback_type *pParam ) 122 | 123 | // Dispatch callbacks relevant to the gameserver client and interfaces. 124 | // To register for these, you need to use STEAM_GAMESERVER_CALLBACK. 125 | // (Or call SetGameserverFlag on your CCallbackBase object.) 126 | S_API void S_CALLTYPE SteamGameServer_RunCallbacks(); 127 | 128 | // Same as STEAM_CALLBACK, but for callbacks on the gameserver interface. 129 | // These will be dispatched during SteamGameServer_RunCallbacks 130 | #define STEAM_GAMESERVER_CALLBACK( thisclass, func, /*callback_type, [deprecated] var*/... ) \ 131 | _STEAM_CALLBACK_SELECT( ( __VA_ARGS__, GS, 3 ), ( this->SetGameserverFlag();, thisclass, func, __VA_ARGS__ ) ) 132 | #define STEAM_GAMESERVER_CALLBACK_MANUAL( thisclass, func, callback_type, var ) \ 133 | CCallbackManual< thisclass, callback_type, true > var; void func( callback_type *pParam ) 134 | 135 | //----------------------------------------------------------------------------- 136 | // Purpose: base for callbacks and call results - internal implementation detail 137 | //----------------------------------------------------------------------------- 138 | class CCallbackBase 139 | { 140 | public: 141 | CCallbackBase() { m_nCallbackFlags = 0; m_iCallback = 0; } 142 | // don't add a virtual destructor because we export this binary interface across dll's 143 | virtual void Run( void *pvParam ) = 0; 144 | virtual void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ) = 0; 145 | int GetICallback() { return m_iCallback; } 146 | virtual int GetCallbackSizeBytes() = 0; 147 | 148 | protected: 149 | enum { k_ECallbackFlagsRegistered = 0x01, k_ECallbackFlagsGameServer = 0x02 }; 150 | uint8 m_nCallbackFlags; 151 | int m_iCallback; 152 | friend class CCallbackMgr; 153 | 154 | private: 155 | CCallbackBase( const CCallbackBase& ); 156 | CCallbackBase& operator=( const CCallbackBase& ); 157 | }; 158 | 159 | //----------------------------------------------------------------------------- 160 | // Purpose: templated base for callbacks - internal implementation detail 161 | //----------------------------------------------------------------------------- 162 | template< int sizeof_P > 163 | class CCallbackImpl : protected CCallbackBase 164 | { 165 | public: 166 | virtual ~CCallbackImpl() { if ( m_nCallbackFlags & k_ECallbackFlagsRegistered ) SteamAPI_UnregisterCallback( this ); } 167 | void SetGameserverFlag() { m_nCallbackFlags |= k_ECallbackFlagsGameServer; } 168 | 169 | protected: 170 | friend class CCallbackMgr; 171 | virtual void Run( void *pvParam ) S_OVERRIDE = 0; 172 | virtual void Run( void *pvParam, bool /*bIOFailure*/, SteamAPICall_t /*hSteamAPICall*/ ) S_OVERRIDE { Run( pvParam ); } 173 | virtual int GetCallbackSizeBytes() S_OVERRIDE { return sizeof_P; } 174 | }; 175 | 176 | 177 | //----------------------------------------------------------------------------- 178 | // Purpose: maps a steam async call result to a class member function 179 | // template params: T = local class, P = parameter struct 180 | //----------------------------------------------------------------------------- 181 | template< class T, class P > 182 | class CCallResult : private CCallbackBase 183 | { 184 | public: 185 | typedef void (T::*func_t)( P*, bool ); 186 | 187 | CCallResult(); 188 | ~CCallResult(); 189 | 190 | void Set( SteamAPICall_t hAPICall, T *p, func_t func ); 191 | bool IsActive() const; 192 | void Cancel(); 193 | 194 | void SetGameserverFlag() { m_nCallbackFlags |= k_ECallbackFlagsGameServer; } 195 | private: 196 | virtual void Run( void *pvParam ) S_OVERRIDE; 197 | virtual void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ) S_OVERRIDE; 198 | virtual int GetCallbackSizeBytes() S_OVERRIDE { return sizeof( P ); } 199 | 200 | SteamAPICall_t m_hAPICall; 201 | T *m_pObj; 202 | func_t m_Func; 203 | }; 204 | 205 | 206 | 207 | //----------------------------------------------------------------------------- 208 | // Purpose: maps a steam callback to a class member function 209 | // template params: T = local class, P = parameter struct, 210 | // bGameserver = listen for gameserver callbacks instead of client callbacks 211 | //----------------------------------------------------------------------------- 212 | template< class T, class P, bool bGameserver = false > 213 | class CCallback : public CCallbackImpl< sizeof( P ) > 214 | { 215 | public: 216 | typedef void (T::*func_t)(P*); 217 | 218 | // NOTE: If you can't provide the correct parameters at construction time, you should 219 | // use the CCallbackManual callback object (STEAM_CALLBACK_MANUAL macro) instead. 220 | CCallback( T *pObj, func_t func ); 221 | 222 | void Register( T *pObj, func_t func ); 223 | void Unregister(); 224 | 225 | protected: 226 | virtual void Run( void *pvParam ) S_OVERRIDE; 227 | 228 | T *m_pObj; 229 | func_t m_Func; 230 | }; 231 | 232 | 233 | //----------------------------------------------------------------------------- 234 | // Purpose: subclass of CCallback which allows default-construction in 235 | // an unregistered state; you must call Register manually 236 | //----------------------------------------------------------------------------- 237 | template< class T, class P, bool bGameServer = false > 238 | class CCallbackManual : public CCallback< T, P, bGameServer > 239 | { 240 | public: 241 | CCallbackManual() : CCallback< T, P, bGameServer >( nullptr, nullptr ) {} 242 | 243 | // Inherits public Register and Unregister functions from base class 244 | }; 245 | 246 | // Internal implementation details for all of the above 247 | #include "steam_api_internal.h" 248 | 249 | #endif // STEAM_API_COMMON_H 250 | --------------------------------------------------------------------------------