├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── protos ├── base_gcmessages.proto ├── cstrike15_gcmessages.proto ├── engine_gcmessages.proto ├── gcsystemmsgs.proto ├── google │ └── protobuf │ │ ├── any.proto │ │ ├── api.proto │ │ ├── descriptor.proto │ │ ├── duration.proto │ │ ├── empty.proto │ │ ├── field_mask.proto │ │ ├── source_context.proto │ │ ├── struct.proto │ │ ├── timestamp.proto │ │ ├── type.proto │ │ └── wrappers.proto ├── netmessages.proto └── steammessages.proto └── src ├── main.rs ├── protoutil ├── mod.rs └── protoutil.rs ├── source ├── bitbuf.rs ├── channel.rs ├── gamelogic.rs ├── ice.rs ├── lzss.rs ├── mod.rs ├── netmessages.rs ├── packetbase.rs ├── packets.rs ├── protos │ ├── mod.rs │ └── netmessages.rs └── subchannel.rs └── steam ├── client.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea/ 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "se-client" 3 | version = "0.1.0" 4 | authors = ["gbps@ctf.re"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [build-dependencies] 9 | protoc-rust = "2.0" 10 | 11 | [dependencies] 12 | csgogcprotos = {git = "https://github.com/Gbps/csgogcprotos-rs"} 13 | anyhow = "1.0.31" 14 | enum_dispatch = "0.3.1" 15 | num-traits = "0.2" 16 | num-derive = "0.3" 17 | protobuf = { version = "2", features = ["with-bytes"] } 18 | bitstream-io = "0.8.5" 19 | libc = "0.2" 20 | steamworks = {git = "https://github.com/Gbps/steamworks-rs.git"} 21 | pretty-hex = "0.1.1" 22 | crc32fast = "1.2.0" 23 | smallvec = { version = "1.4.2", features = ['write'] } 24 | log = { version = "0.4", features = ["max_level_trace", "release_max_level_warn"] } 25 | pretty_env_logger = "0.4.0" 26 | byteorder = "1.3" 27 | 28 | [profile.release] 29 | debug = true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WIP WIP WIP WIP 2 | 3 | # It does work! 4 | 5 | A full game authentication is performed with Steam's GC as well as the game server itself. Net messages have been successfully sent and received with a community game server. 6 | 7 | ``` 8 | TRACE se_client::source::subchannel > Fragments successfully decompressed 9 | TRACE se_client::source::channel > --- read_messages() begin --- 10 | TRACE se_client::source::channel > MESSAGE [id=16, size=113]: <-- svc_Print 11 | TRACE se_client::source::channel > MESSAGE [id=34, size=518]: <-- svc_CmdKeyValues 12 | TRACE se_client::source::channel > MESSAGE [id=8, size=114]: <-- svc_ServerInfo 13 | TRACE se_client::source::channel > MESSAGE [id=4, size=8]: <-- net_Tick 14 | TRACE se_client::source::channel > MESSAGE [id=12, size=2630]: <-- svc_CreateStringTable 15 | TRACE se_client::source::channel > MESSAGE [id=12, size=12502]: <-- svc_CreateStringTable 16 | TRACE se_client::source::channel > MESSAGE [id=12, size=34]: <-- svc_CreateStringTable 17 | TRACE se_client::source::channel > MESSAGE [id=12, size=43067]: <-- svc_CreateStringTable 18 | ``` 19 | 20 | # Emulating a source client signon 21 | 22 | Source engine's signon process has gotten significantly more complicated over the years as CS:GO has transitioned to matchmaking rather than direct IP connection. In addition, the invention of Steam Game Sockets means that now the traffic that's being communicated between the client and the server is being proxied over a relay network embedded in the Steam backbone. 23 | 24 | For this purpose, I'm focusing on the most basic kind of gameserver connection, one that is done directly over UDP to a target port. 25 | 26 | For this project, I decided to use Rust to implement the networking... because why not, it's fun and I'm learning the new language. 27 | 28 | ## Connectionless Packets 29 | 30 | Source engine has two kinds of communications with a game client, Connectionless and NetChan. Both of these happen over UDP. Connectionless packets are plain unencrypted UDP packets with a 4 byte `0xFFFFFFFF` header specifying that they are connectionless. Anyone can send a connectionless packet to a game server's UDP port (typically 27015). Typically this is used for querying information about the server before forming an actual connection, such as the legacy server browser which queried information about the current state and map of the server and displays it in a UI before the user connects. 31 | 32 | ## Forming a new connection: Challenge 33 | 34 | When *not* using matchmaking, everything about a connection begins from a client when the `connect` concommand (or similar) is executed pointing the client to connect to a certain ip and port. This bubbles down to the following function: 35 | 36 | ```c++ 37 | (baseclientstate.cpp:1058): 38 | void CBaseClientState::ConnectInternal( const char *pchPublicAddress, char const *pchPrivateAddress, int numPlayers, const char* szJoinType ) 39 | ``` 40 | 41 | Here is the first instance of the function `SetSignonState` being run, which sets the current state of the state machine of the handshake between the server and the client. 42 | 43 | ```c++ 44 | (baseclientstate.cpp:1089): 45 | SetSignonState( SIGNONSTATE_CHALLENGE, -1, NULL ); 46 | ``` 47 | 48 | Here are all of the states the client and server can be in for a single client connection. At all points of the process, the client and server must agree at what signon state the handshake is in or the process fails. 49 | 50 | ```c++ 51 | enum SIGNONSTATE 52 | { 53 | SIGNONSTATE_NONE = 0, // no state yet; about to connect 54 | SIGNONSTATE_CHALLENGE = 1, // client challenging server; all OOB packets 55 | SIGNONSTATE_CONNECTED = 2, // client is connected to server; netchans ready 56 | SIGNONSTATE_NEW = 3, // just got serverinfo and string tables 57 | SIGNONSTATE_PRESPAWN = 4, // received signon buffers 58 | SIGNONSTATE_SPAWN = 5, // ready to receive entity packets 59 | SIGNONSTATE_FULL = 6, // we are fully connected; first non-delta packet received 60 | SIGNONSTATE_CHANGELEVEL = 7, // server is changing level; please wait 61 | }; 62 | ``` 63 | 64 | > OOB packets are synonymous with connectionless packets 65 | 66 | This queues the client to begin sending packets to the server requesting a challenge. The actual request of the challenge happens here: 67 | 68 | ```c++ 69 | (baseclientstate.cpp:1381): 70 | void CBaseClientState::CheckForResend ( bool bForceResendNow /* = false */ ) 71 | ``` 72 | 73 | This function is responsible for repeatedly poking the server and asking for a connection challenge. The packet used to request this challenge is `A2S_GETCHALLENGE` and the payload is of the pseudo-structure form: 74 | 75 | ``` 76 | { 77 | CONNECTIONLESS_HEADER: u32 78 | TYPE: u8 = A2S_GETCHALLENGE 79 | CONNECTION_STRING: String = "connect0xAABBCCDD" 80 | } 81 | ``` 82 | 83 | Where the connection string is of the format `connect0x%08X` appending a 4-byte challenge to the message. This challenge is always equal to the *last* challenge value received from *any* server that the client tried to connect to. Otherwise, if the client just launched, this value is equal to `0x00000000`. 84 | 85 | Now, the server receives the OOB `A2S_GETCHALLENGE` from a client and processes the inner message to see that it is a `connect` message. It then builds a response, of OOB type `S2C_CHALLENGE`. This takes place in: 86 | 87 | ```cpp 88 | (baseserver.cpp:1631): 89 | void CBaseServer::ReplyChallenge( const ns_address &adr, bf_read &inmsg ) 90 | ``` 91 | 92 | The server then randomly generates a challenge number to use for the connection and stores it into a large vector of all challenges for all clients that have ever tried to initiate a connection. 93 | 94 | The expected result is that the server will respond with `connect-retry` and the cookie the server wants the client to send. Then, on the next attempt, the client will try again but with the requested value. The server will then accept it and respond with a context of `connect` instead. 95 | 96 | It then writes back the response: 97 | 98 | * [32] Connectionless Header 99 | * [8] Type of connectionless packet 100 | * [32] Randomly generated challenge number from above 101 | * [32] Auth protocol, always `PROTOCOL_STEAM=0x03` 102 | * [16] Steam2 encryption enabled bool? 1/0 (Always 0 now, a different kind of encryption is used) 103 | * [64] Steam gameserver steamid 104 | * [8] Is the game server VAC secured? 1/0 105 | 106 | > PROTOCOL_STEAM is always used over PROTOCOL_HASHEDCDKEY except if the server is a listen server on a client which has no steam connection 107 | 108 | Gameservers now all have their own steam id, either linked with a steam account or using an anonymous steam id. It can be used to uniquely identify a server, regardless of IP. 109 | 110 | Next is the response of the the challenge which determines if the client is allowed to connect. A few factors go into this decision 111 | 112 | * Is the server locked to only allow certain lobbies to join? 113 | 114 | * If so, check to make sure the challenge value sent by the client is correct. If not, respond `connect-retry`. 115 | 116 | * If direct connections are not allowed: 117 | * If it is a Valve Dedicated Server, respond with `connect-matchmaking-only` since Valve DS do not support direct connections, only connections made through their matchmaking system. This is particularly more difficult to trigger now because all Valve DS now hide behind the Steam Socket relays, which means packets are routed directly to the game server through the relay and not over the public internet. More investigation on this later. 118 | * Otherwise, if it's not a Valve DS, respond with `connect-lan-only` meaning it is a community CS:GO server which is locked down for only LAN connections. 119 | 120 | * Otherwise, if the server isn't lobby only, just respond with the requested context (`connect0x....` same as requested context) 121 | 122 | * [32] The host version 123 | * [String] The lobby type ("" if unsuccessful, "public" if successful) 124 | * [8] Password required? 1/0 125 | * *some extra valve-specific matchmaking logic* 126 | * [64] Lobby id (always -1 unless lobbies are in use) 127 | * [8] Friends required? (always 0) 128 | * [8] Is valve dedicated server? 1/0 129 | * [8] Requires certificate authentication? 1/0 (should always be 0 unless it's a *special* community game server... maybe something like FACEIT?) 130 | * If certificate authentication is requested writes the following: 131 | * [32] size of public key 132 | * [y bytes] where y is the size of the public key 133 | * [32] size of encryption signature 134 | * [z bytes] where z is the size of the encryption signature 135 | 136 | That's a big packet. 137 | 138 | So at this point it should look like this: 139 | 140 | * [client sends A2S_GETCHALLENGE and empty challenge value] 141 | 142 | * [server responds with a random challenge in S2C_CHALLENGE] 143 | 144 | * [client responds with server's challenge in another A2S_GETCHALLENGE] 145 | 146 | * [server responds with success in another S2C_CHALLENGE] 147 | 148 | and now both sides have verified the challegne. 149 | 150 | Here is a dump of a successful challenge: 151 | 152 | ``` 153 | [src\main.rs:35] &packet = A2sGetChallenge { 154 | connect_string: "connect0x00000000", 155 | } 156 | [src\main.rs:40] &_res = S2cChallenge { 157 | challenge_num: 233306117, 158 | auth_protocol: PROTOCOL_STEAM, 159 | steam2_encryption_enabled: 0, 160 | gameserver_steamid: 90136361812869131, 161 | vac_secured: 0, 162 | context_response: "connect-retry", 163 | host_version: 13758, 164 | lobby_type: "public", 165 | password_required: 0, 166 | reservation_cookie: 18446744073709551615, 167 | friends_required: 0, 168 | valve_ds: 0, 169 | require_certificate: 0, 170 | } 171 | [src\main.rs:44] &packet = A2sGetChallenge { 172 | connect_string: "connect0x0de7f805", 173 | } 174 | [src\main.rs:49] &_res = S2cChallenge { 175 | challenge_num: 233306117, 176 | auth_protocol: PROTOCOL_STEAM, 177 | steam2_encryption_enabled: 0, 178 | gameserver_steamid: 90136361812869131, 179 | vac_secured: 0, 180 | context_response: "connect0x0de7f805", 181 | host_version: 13758, 182 | lobby_type: "public", 183 | password_required: 0, 184 | reservation_cookie: 18446744073709551615, 185 | friends_required: 0, 186 | valve_ds: 0, 187 | require_certificate: 0, 188 | } 189 | ``` 190 | 191 | ## Connect packet + NetChannel creation 192 | 193 | Once the challenge handshake is complete, the client calls into: 194 | 195 | ```cpp 196 | void CBaseClientState::SendConnectPacket ( const ns_address &netAdrRemote, int challengeNr, int authProtocol, uint64 unGSSteamID, bool bGSSecure ) 197 | ``` 198 | 199 | to send the `C2S_CONNECT` packet to initiate a netchannel. The connect packet contains extra misc. information about the client. The important part of this packet is the User Info block, which is responsible for encoding all of the CVars on the client marked with `FCVAR_USERINFO`. All of these cvars are marked as such because the server wants to be able to query these without having to do a roundtrip with the client. An example of an `FCVAR_USERINFO` CVar would be `name`, which stores the name of the player they want to use. 200 | 201 | This packet is the first instance of Protobuf packets being used in the connection. In the CS:GO version of the engine and beyond, most all packet communication is done using Protobuf packets. Prior to the introduction of Protobuf, everything was done manually by writing and reading values from buffers similarly to how the Connectionless packets still function. Now Protobuf handles that automatically. 202 | 203 | This packet is especially curious because it is not a Protobuf packet in itself, but it contains an embedded Protobuf packet. Specifically, it contains the Protobuf packet called `CCLCMsg_SplitPlayerConnect`, which stores all of the User Info CVars talked about previously. Only cvars actually modified from their default value will be sent, otherwise it is assumed on the server to be default values. For each split player connecting, there will be a `CCLCMsg_SplitPlayerConnect` protobuf packet encoded into the packet. All CVars are sent as strings, even if their actual values are integers or floats. The server will interpret these string values as any kind of integer value when it receives the cvars. 204 | 205 | The protobuf definition is given to us from Valve: 206 | 207 | ```protobuf 208 | message CCLCMsg_SplitPlayerConnect 209 | { 210 | optional CMsg_CVars convars = 1; 211 | } 212 | ``` 213 | 214 | The actual CVars are iterated and added to the Protobuf packet in the function: 215 | 216 | ```cpp 217 | Host_BuildUserInfoUpdateMessage( playerCount, splitMsg.mutable_convars(), false ); 218 | ``` 219 | 220 | Something special about this protobuf message is that the different cvars can be encoded into an index form instead of a full name. These cvars are hardcoded the list appears to include all of the userinfo cvars that are typically sent as part of a connection. Here is the list of all cvars that are encoded this way: 221 | 222 | * accountid 223 | * password 224 | * cl_use_opens_buy_menu 225 | * tv_nochat 226 | * cl_clanid 227 | * name 228 | * cl_interp_ratio 229 | * cl_predict 230 | * cl_updaterate 231 | * cl_session 232 | * voice_loopback 233 | * cl_lagcompensation 234 | * cl_color 235 | * cl_cmdrate 236 | * net_maxroutable 237 | * rate 238 | * cl_predictweapons 239 | * cl_autohelp 240 | * cl_interp 241 | * cl_autowepswitch 242 | * cl_spec_mode 243 | * tv_relay 244 | * hltv_slots 245 | * hltv_clients 246 | * hltv_addr 247 | * hltv_proxies 248 | * sv_bot_difficulty_kbm 249 | * hltv_sdr 250 | * steamworks_sessionid_client 251 | * sdr_routing 252 | 253 | These can of course also be sent by name rather by index. This seems to mostly be done for performance reasons. 254 | 255 | In addition, this is where the Steam authentication process begins. 256 | 257 | The call to `GetAuthSessionTicket` is a steamapi function which `Retrieve ticket to be sent to the entity who wishes to authenticate you.` 258 | 259 | The total auth buffer is a combination of: 260 | 261 | ``` 262 | [64] int64 steamid 263 | [X ] auth session ticket 264 | [64] size of ticket + steamid 265 | ``` 266 | 267 | Then the auth buffer is written in the following form: 268 | 269 | ``` 270 | [16] Size of steam cookie 271 | [X] Auth buffer 272 | ``` 273 | 274 | A curious part of this entire auth buffer is that it has two separate sizes, one for the cookie entirely and another for the size of the ticket itself. 275 | 276 | Here's the format: 277 | 278 | * [32] Connectionless Packet Header 279 | 280 | * [8] C2S_CONNECT 281 | 282 | * [32] Host version (in CS:GO this always matches the server, since this version is checked later) 283 | 284 | * [32] Authentication protocol (should match server, always `PROTOCOL_STEAM`) 285 | 286 | * [32] Challenge number (same from the challenge from `S2C_CHALLENGE`) 287 | 288 | * [String] Player name. Not used in CS:GO, this is read from the user info instead. 289 | 290 | * [String] Server password to authenticate with, if one is used. 291 | 292 | * [8] The number of players preparing to connect. In split screen this could be 2, but typically it is 1. 293 | 294 | * [X ] The inline encoded `CCLCMsg_SplitPlayerConnect` for each player (This uses Source Engine's own wrapper for netmessage packets) 295 | 296 | * [1] Low violence enabled [NOTE: this is a *single* bit] 297 | 298 | * [64] Server reservation cookie 299 | 300 | * [8] Current crossplay platform 301 | 302 | * ``` 303 | enum CrossPlayPlatform_t 304 | { 305 | CROSSPLAYPLATFORM_UNKNOWN = 0, 306 | CROSSPLAYPLATFORM_PC, 307 | CROSSPLAYPLATFORM_X360, 308 | CROSSPLAYPLATFORM_PS3, 309 | 310 | CROSSPLAYPLATFORM_LAST = CROSSPLAYPLATFORM_PS3, 311 | }; 312 | ``` 313 | 314 | * [32] If no certificate encryption is used, a 0 is written here. 315 | 316 | * [X ] Steam authentication buffer 317 | 318 | * [16] Size of following fields 319 | * [64] int64 steamid 320 | * [X ] auth session ticket returned from steam api 321 | * [64] size of ticket + steamid 322 | 323 | ``` 324 | -> Reservation cookie 9a2a387bc911bda3: reason [R] Connect from 192.168.1.100:27005 325 | ``` 326 | 327 | ## On Fragments 328 | So most of my time implementing subchannels is learning how fragments actually work. Here's what I've learned so far: 329 | 330 | * There are 8 subchannels and 2 streams. 331 | * A stream is either a message (default) steam (index=0) or a file stream (index=1). 332 | * A message stream sends reliable netmessages (large) 333 | * In this case, I believe it's sending the server info and delta updates to us all at once 334 | * The message stream being sent to me has been compressed at the fragment level. Basically LZSS compression is done on the entire payload prior to starting the send. At the beginning of the start of a new transfer on a stream, the sending end alerts the receiving end of the uncompressed and compressed sizes, then starts to send the compressed data in fragments. 335 | * Fragments are 256 byte chunks of data of the overall payload. Can think of the total payload as being split up into these fragment chunks. So rather than transferring to 336 | * A subchannel is a single instance of a set of fragments being sent. There are 8 of them, one bit to fill the byte in the header. 337 | * 338 | * Basically, you can think of these like 8 simultaneous transfers, where each individual transfer can mark itself as completed when it's received 339 | * This is because UDP is not a reliable protocol, so out of order and lost fragments need to be marked and recovered from. 340 | * When sending over a set of, for example, 4 fragments (fragment size = 256, total size = 1024), it will send over a free subchannel (starts at 0). Then when the receiver gets it, it will mark the 0th index bit in its `reliable_state` on its next packet it sends off. Then, when the sender sends the next 4 fragments, it will send it over the next free subchannel (index 1). This will repeat with the sender rotating the free subchannels and the receiver flipping the bit in the reliable state. 341 | 342 | # Signon: CONNECTED -> NEW 343 | 344 | So, now that we've authenticated and connected and formed a netchannel, the first thing the client must do is send a set signon message to set the signon to `CONNECTED`. This initiates the server to send the set of reliable netmessages to get the player ready to spawn in on their side. 345 | 346 | Here are all of the messages sent in the reliable (subchannel) buffer: 347 | 348 | * svc_Print - Prints some general info about the server to the client's console: 349 | 350 | * ``` 351 | Counter-Strike: Global Offensive 352 | Map: de_dust2 353 | Players: 1 (0 bots) / 20 humans 354 | Build: 8012 355 | Server Number: 88 356 | ``` 357 | 358 | * svc_CmdKeyValues - `TODO` 359 | 360 | * svc_ServerInfo - Contains info about the entity classes and some additional things like the map name and skybox name. Mostly just misc things. 361 | 362 | * ``` 363 | Server Info: protocol: 13770 server_count: 88 is_dedicated: true is_hltv: false is_redirecting_to_proxy_relay: false c_os: 119 map_crc: 282161188 client_crc: 1473533612 string_table_crc: 0 max_clients: 64 max_classes: 283 player_slot: 0 tick_interval: 0.015625 game_dir: "csgo" map_name: "de_dust2" map_group_name: "" sky_name: "nukeblank" host_name: "Counter-Strike: Global Offensive" ugc_map_id: 0 364 | ``` 365 | 366 | * net_Tick - Synchronize the server's tick count 367 | 368 | * net_SetConVar - A message containing all of the replicated convars set by the server: 369 | 370 | * ``` 371 | DEBUG se_client::source::gamelogic > Set ConVar: [name=bot_autodifficulty_threshold_high] [value=0] 372 | DEBUG se_client::source::gamelogic > Set ConVar: [name=cash_team_win_by_defusing_bomb] [value=2700] 373 | [...] 374 | ``` 375 | 376 | * svc_CreateStringTable - a create string table message for each of the string tables on the server, including its data 377 | 378 | * downloadables 379 | * modelprecache 380 | * genericprecache 381 | * soundprecache 382 | * decalprecache 383 | * instancebaseline 384 | * lightstyles 385 | * userinfo 386 | * dynamicmodel 387 | * server_query_info 388 | * ExtraParticleFilesTable 389 | * ParticleEffectNames 390 | * EffectDispatch 391 | * VguiScreen 392 | * Materials 393 | * InfoPanel 394 | * Scenes 395 | * Movies 396 | * GameRulesCreation 397 | 398 | * svc_SignonState - Sets the client's signon state from CONNECTED to NEW. 399 | 400 | # Signon (client): CONNECTED (old) -> NEW (new) 401 | 402 | When the above finishes, next the client calls `SendClientInfo` which constructs a `CCLCMsg_ClientInfo` to send to the server. Fields here include: 403 | 404 | - the SendTable CRC 405 | - The "server count" (kind of like a uid for this particular attempt to connect) 406 | - is_hltv 407 | - is_replay 408 | - friends id (from steam) 409 | - friends name (from steam) 410 | - any custom files (sprays) 411 | 412 | Then the client sends a `net_SignonState` for `NEW` containing the spawn_count from the last `net_SignonState` received. This must always be sent on new signon updates, else the server will force a reconnect. 413 | 414 | Unfortunately, there is no good way to calculate the send tables CRC on the client. 415 | 416 | # Signon (server): NEW (old) -> PRESPAWN (new) 417 | 418 | Server verifies the class tables CRC to match its own. If it's invalid (aka the client binary is out of date) it will disconnect with `Server uses different class tables`. This is what spawned me to write the `crcgrab.exe` project which signature scans for the class table crc value and then ReadProcessMemory's it from a real csgo.exe instance. 419 | 420 | Server sends the signon data buffer to the client. This signon data is the same thing sent to all clients. It consists of every call to `BroadcastMessage` with `IsInitMessage` set made by the server during this map load. This catches up the client to everything that happened. I am not convinced there are any packets marked with `FLAG_INIT_MESSAGE` right now in the engine. 421 | 422 | The server sends a signon state `PRESPAWN` to client. 423 | 424 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate protoc_rust; 2 | 3 | fn main() { 4 | protoc_rust::Codegen::new() 5 | .out_dir("src/source/protos") 6 | .inputs(&["protos/netmessages.proto"]) 7 | .include("protos") 8 | .run() 9 | .expect("protoc"); 10 | } -------------------------------------------------------------------------------- /protos/base_gcmessages.proto: -------------------------------------------------------------------------------- 1 | import "steammessages.proto"; 2 | 3 | option optimize_for = SPEED; 4 | option cc_generic_services = false; 5 | 6 | enum EGCBaseMsg { 7 | k_EMsgGCSystemMessage = 4001; 8 | k_EMsgGCReplicateConVars = 4002; 9 | k_EMsgGCConVarUpdated = 4003; 10 | k_EMsgGCInQueue = 4008; 11 | k_EMsgGCInviteToParty = 4501; 12 | k_EMsgGCInvitationCreated = 4502; 13 | k_EMsgGCPartyInviteResponse = 4503; 14 | k_EMsgGCKickFromParty = 4504; 15 | k_EMsgGCLeaveParty = 4505; 16 | k_EMsgGCServerAvailable = 4506; 17 | k_EMsgGCClientConnectToServer = 4507; 18 | k_EMsgGCGameServerInfo = 4508; 19 | k_EMsgGCError = 4509; 20 | k_EMsgGCReplay_UploadedToYouTube = 4510; 21 | k_EMsgGCLANServerAvailable = 4511; 22 | } 23 | 24 | enum EGCBaseProtoObjectTypes { 25 | k_EProtoObjectPartyInvite = 1001; 26 | k_EProtoObjectLobbyInvite = 1002; 27 | } 28 | 29 | enum GC_BannedWordType { 30 | GC_BANNED_WORD_DISABLE_WORD = 0; 31 | GC_BANNED_WORD_ENABLE_WORD = 1; 32 | } 33 | 34 | message CGCStorePurchaseInit_LineItem { 35 | optional uint32 item_def_id = 1; 36 | optional uint32 quantity = 2; 37 | optional uint32 cost_in_local_currency = 3; 38 | optional uint32 purchase_type = 4; 39 | } 40 | 41 | message CMsgGCStorePurchaseInit { 42 | optional string country = 1; 43 | optional int32 language = 2; 44 | optional int32 currency = 3; 45 | repeated .CGCStorePurchaseInit_LineItem line_items = 4; 46 | } 47 | 48 | message CMsgGCStorePurchaseInitResponse { 49 | optional int32 result = 1; 50 | optional uint64 txn_id = 2; 51 | optional string url = 3; 52 | repeated uint64 item_ids = 4; 53 | } 54 | 55 | message CSOPartyInvite { 56 | optional uint64 group_id = 1 [(key_field) = true]; 57 | optional fixed64 sender_id = 2; 58 | optional string sender_name = 3; 59 | } 60 | 61 | message CSOLobbyInvite { 62 | optional uint64 group_id = 1 [(key_field) = true]; 63 | optional fixed64 sender_id = 2; 64 | optional string sender_name = 3; 65 | } 66 | 67 | message CMsgSystemBroadcast { 68 | optional string message = 1; 69 | } 70 | 71 | message CMsgInviteToParty { 72 | optional fixed64 steam_id = 1; 73 | optional uint32 client_version = 2; 74 | optional uint32 team_invite = 3; 75 | } 76 | 77 | message CMsgInvitationCreated { 78 | optional uint64 group_id = 1; 79 | optional fixed64 steam_id = 2; 80 | } 81 | 82 | message CMsgPartyInviteResponse { 83 | optional uint64 party_id = 1; 84 | optional bool accept = 2; 85 | optional uint32 client_version = 3; 86 | optional uint32 team_invite = 4; 87 | } 88 | 89 | message CMsgKickFromParty { 90 | optional fixed64 steam_id = 1; 91 | } 92 | 93 | message CMsgLeaveParty { 94 | } 95 | 96 | message CMsgServerAvailable { 97 | } 98 | 99 | message CMsgLANServerAvailable { 100 | optional fixed64 lobby_id = 1; 101 | } 102 | 103 | message CSOEconGameAccountClient { 104 | optional uint32 additional_backpack_slots = 1 [default = 0]; 105 | optional fixed32 bonus_xp_timestamp_refresh = 12; 106 | optional uint32 bonus_xp_usedflags = 13; 107 | optional uint32 elevated_state = 14; 108 | optional uint32 elevated_timestamp = 15; 109 | } 110 | 111 | message CSOItemCriteriaCondition { 112 | optional int32 op = 1; 113 | optional string field = 2; 114 | optional bool required = 3; 115 | optional float float_value = 4; 116 | optional string string_value = 5; 117 | } 118 | 119 | message CSOItemCriteria { 120 | optional uint32 item_level = 1; 121 | optional int32 item_quality = 2; 122 | optional bool item_level_set = 3; 123 | optional bool item_quality_set = 4; 124 | optional uint32 initial_inventory = 5; 125 | optional uint32 initial_quantity = 6; 126 | optional bool ignore_enabled_flag = 8; 127 | repeated .CSOItemCriteriaCondition conditions = 9; 128 | optional int32 item_rarity = 10; 129 | optional bool item_rarity_set = 11; 130 | optional bool recent_only = 12; 131 | } 132 | 133 | message CSOItemRecipe { 134 | optional uint32 def_index = 1; 135 | optional string name = 2; 136 | optional string n_a = 3; 137 | optional string desc_inputs = 4; 138 | optional string desc_outputs = 5; 139 | optional string di_a = 6; 140 | optional string di_b = 7; 141 | optional string di_c = 8; 142 | optional string do_a = 9; 143 | optional string do_b = 10; 144 | optional string do_c = 11; 145 | optional bool requires_all_same_class = 12; 146 | optional bool requires_all_same_slot = 13; 147 | optional int32 class_usage_for_output = 14; 148 | optional int32 slot_usage_for_output = 15; 149 | optional int32 set_for_output = 16; 150 | repeated .CSOItemCriteria input_items_criteria = 20; 151 | repeated .CSOItemCriteria output_items_criteria = 21; 152 | repeated uint32 input_item_dupe_counts = 22; 153 | } 154 | 155 | message CMsgDevNewItemRequest { 156 | optional fixed64 receiver = 1; 157 | optional .CSOItemCriteria criteria = 2; 158 | } 159 | 160 | message CMsgIncrementKillCountAttribute { 161 | optional fixed32 killer_account_id = 1; 162 | optional fixed32 victim_account_id = 2; 163 | optional uint64 item_id = 3; 164 | optional uint32 event_type = 4; 165 | optional uint32 amount = 5; 166 | } 167 | 168 | message CMsgApplySticker { 169 | optional uint64 sticker_item_id = 1; 170 | optional uint64 item_item_id = 2; 171 | optional uint32 sticker_slot = 3; 172 | optional uint32 baseitem_defidx = 4; 173 | optional float sticker_wear = 5; 174 | } 175 | 176 | message CMsgModifyItemAttribute { 177 | optional uint64 item_id = 1; 178 | optional uint32 attr_defidx = 2; 179 | optional uint32 attr_value = 3; 180 | } 181 | 182 | message CMsgApplyStatTrakSwap { 183 | optional uint64 tool_item_id = 1; 184 | optional uint64 item_1_item_id = 2; 185 | optional uint64 item_2_item_id = 3; 186 | } 187 | 188 | message CMsgApplyStrangePart { 189 | optional uint64 strange_part_item_id = 1; 190 | optional uint64 item_item_id = 2; 191 | } 192 | 193 | message CMsgApplyPennantUpgrade { 194 | optional uint64 upgrade_item_id = 1; 195 | optional uint64 pennant_item_id = 2; 196 | } 197 | 198 | message CMsgApplyEggEssence { 199 | optional uint64 essence_item_id = 1; 200 | optional uint64 egg_item_id = 2; 201 | } 202 | 203 | message CSOEconItemAttribute { 204 | optional uint32 def_index = 1; 205 | optional uint32 value = 2; 206 | optional bytes value_bytes = 3; 207 | } 208 | 209 | message CSOEconItemEquipped { 210 | optional uint32 new_class = 1; 211 | optional uint32 new_slot = 2; 212 | } 213 | 214 | message CSOEconItem { 215 | optional uint64 id = 1; 216 | optional uint32 account_id = 2; 217 | optional uint32 inventory = 3; 218 | optional uint32 def_index = 4; 219 | optional uint32 quantity = 5; 220 | optional uint32 level = 6; 221 | optional uint32 quality = 7; 222 | optional uint32 flags = 8 [default = 0]; 223 | optional uint32 origin = 9; 224 | optional string custom_name = 10; 225 | optional string custom_desc = 11; 226 | repeated .CSOEconItemAttribute attribute = 12; 227 | optional .CSOEconItem interior_item = 13; 228 | optional bool in_use = 14 [default = false]; 229 | optional uint32 style = 15 [default = 0]; 230 | optional uint64 original_id = 16 [default = 0]; 231 | repeated .CSOEconItemEquipped equipped_state = 18; 232 | optional uint32 rarity = 19; 233 | } 234 | 235 | message CMsgAdjustItemEquippedState { 236 | optional uint64 item_id = 1; 237 | optional uint32 new_class = 2; 238 | optional uint32 new_slot = 3; 239 | optional bool swap = 4; 240 | } 241 | 242 | message CMsgAdjustItemEquippedStateMulti { 243 | repeated uint64 t_equips = 1; 244 | repeated uint64 ct_equips = 2; 245 | repeated uint64 noteam_equips = 3; 246 | } 247 | 248 | message CMsgSortItems { 249 | optional uint32 sort_type = 1; 250 | } 251 | 252 | message CSOEconClaimCode { 253 | optional uint32 account_id = 1; 254 | optional uint32 code_type = 2; 255 | optional uint32 time_acquired = 3; 256 | optional string code = 4; 257 | } 258 | 259 | message CMsgStoreGetUserData { 260 | optional fixed32 price_sheet_version = 1; 261 | optional int32 currency = 2; 262 | } 263 | 264 | message CMsgStoreGetUserDataResponse { 265 | optional int32 result = 1; 266 | optional int32 currency_deprecated = 2; 267 | optional string country_deprecated = 3; 268 | optional fixed32 price_sheet_version = 4; 269 | optional bytes price_sheet = 8; 270 | } 271 | 272 | message CMsgUpdateItemSchema { 273 | optional bytes items_game = 1; 274 | optional fixed32 item_schema_version = 2; 275 | optional string items_game_url_DEPRECATED2013 = 3; 276 | optional string items_game_url = 4; 277 | } 278 | 279 | message CMsgGCError { 280 | optional string error_text = 1; 281 | } 282 | 283 | message CMsgRequestInventoryRefresh { 284 | } 285 | 286 | message CMsgConVarValue { 287 | optional string name = 1; 288 | optional string value = 2; 289 | } 290 | 291 | message CMsgReplicateConVars { 292 | repeated .CMsgConVarValue convars = 1; 293 | } 294 | 295 | message CMsgUseItem { 296 | optional uint64 item_id = 1; 297 | optional fixed64 target_steam_id = 2; 298 | repeated uint32 gift__potential_targets = 3; 299 | optional uint32 duel__class_lock = 4; 300 | optional fixed64 initiator_steam_id = 5; 301 | } 302 | 303 | message CMsgReplayUploadedToYouTube { 304 | optional string youtube_url = 1; 305 | optional string youtube_account_name = 2; 306 | optional uint64 session_id = 3; 307 | } 308 | 309 | message CMsgConsumableExhausted { 310 | optional int32 item_def_id = 1; 311 | } 312 | 313 | message CMsgItemAcknowledged__DEPRECATED { 314 | optional uint32 account_id = 1; 315 | optional uint32 inventory = 2; 316 | optional uint32 def_index = 3; 317 | optional uint32 quality = 4; 318 | optional uint32 rarity = 5; 319 | optional uint32 origin = 6; 320 | optional uint64 item_id = 7; 321 | } 322 | 323 | message CMsgSetItemPositions { 324 | message ItemPosition { 325 | optional uint32 legacy_item_id = 1; 326 | optional uint32 position = 2; 327 | optional uint64 item_id = 3; 328 | } 329 | 330 | repeated .CMsgSetItemPositions.ItemPosition item_positions = 1; 331 | } 332 | 333 | message CMsgGCReportAbuse { 334 | optional fixed64 target_steam_id = 1; 335 | optional string description = 4; 336 | optional uint64 gid = 5; 337 | optional uint32 abuse_type = 2; 338 | optional uint32 content_type = 3; 339 | optional fixed32 target_game_server_ip = 6; 340 | optional uint32 target_game_server_port = 7; 341 | } 342 | 343 | message CMsgGCReportAbuseResponse { 344 | optional fixed64 target_steam_id = 1; 345 | optional uint32 result = 2; 346 | optional string error_message = 3; 347 | } 348 | 349 | message CMsgGCNameItemNotification { 350 | optional fixed64 player_steamid = 1; 351 | optional uint32 item_def_index = 2; 352 | optional string item_name_custom = 3; 353 | } 354 | 355 | message CMsgGCClientDisplayNotification { 356 | optional string notification_title_localization_key = 1; 357 | optional string notification_body_localization_key = 2; 358 | repeated string body_substring_keys = 3; 359 | repeated string body_substring_values = 4; 360 | } 361 | 362 | message CMsgGCShowItemsPickedUp { 363 | optional fixed64 player_steamid = 1; 364 | } 365 | 366 | message CMsgGCIncrementKillCountResponse { 367 | optional uint32 killer_account_id = 1 [(key_field) = true]; 368 | optional uint32 num_kills = 2; 369 | optional uint32 item_def = 3; 370 | optional uint32 level_type = 4; 371 | } 372 | 373 | message CSOEconItemDropRateBonus { 374 | optional uint32 account_id = 1; 375 | optional fixed32 expiration_date = 2; 376 | optional float bonus = 3; 377 | optional uint32 bonus_count = 4; 378 | optional uint64 item_id = 5; 379 | optional uint32 def_index = 6; 380 | } 381 | 382 | message CSOEconItemLeagueViewPass { 383 | optional uint32 account_id = 1 [(key_field) = true]; 384 | optional uint32 league_id = 2 [(key_field) = true]; 385 | optional uint32 admin = 3; 386 | optional uint32 itemindex = 4; 387 | } 388 | 389 | message CSOEconItemEventTicket { 390 | optional uint32 account_id = 1; 391 | optional uint32 event_id = 2; 392 | optional uint64 item_id = 3; 393 | } 394 | 395 | message CMsgGCItemPreviewItemBoughtNotification { 396 | optional uint32 item_def_index = 1; 397 | } 398 | 399 | message CMsgGCStorePurchaseCancel { 400 | optional uint64 txn_id = 1; 401 | } 402 | 403 | message CMsgGCStorePurchaseCancelResponse { 404 | optional uint32 result = 1; 405 | } 406 | 407 | message CMsgGCStorePurchaseFinalize { 408 | optional uint64 txn_id = 1; 409 | } 410 | 411 | message CMsgGCStorePurchaseFinalizeResponse { 412 | optional uint32 result = 1; 413 | repeated uint64 item_ids = 2; 414 | } 415 | 416 | message CMsgGCBannedWordListRequest { 417 | optional uint32 ban_list_group_id = 1; 418 | optional uint32 word_id = 2; 419 | } 420 | 421 | message CMsgGCRequestAnnouncements { 422 | } 423 | 424 | message CMsgGCRequestAnnouncementsResponse { 425 | optional string announcement_title = 1; 426 | optional string announcement = 2; 427 | optional string nextmatch_title = 3; 428 | optional string nextmatch = 4; 429 | } 430 | 431 | message CMsgGCBannedWord { 432 | optional uint32 word_id = 1; 433 | optional .GC_BannedWordType word_type = 2 [default = GC_BANNED_WORD_DISABLE_WORD]; 434 | optional string word = 3; 435 | } 436 | 437 | message CMsgGCBannedWordListResponse { 438 | optional uint32 ban_list_group_id = 1; 439 | repeated .CMsgGCBannedWord word_list = 2; 440 | } 441 | 442 | message CMsgGCToGCBannedWordListBroadcast { 443 | optional .CMsgGCBannedWordListResponse broadcast = 1; 444 | } 445 | 446 | message CMsgGCToGCBannedWordListUpdated { 447 | optional uint32 group_id = 1; 448 | } 449 | 450 | message CSOEconDefaultEquippedDefinitionInstanceClient { 451 | optional uint32 account_id = 1 [(key_field) = true]; 452 | optional uint32 item_definition = 2; 453 | optional uint32 class_id = 3 [(key_field) = true]; 454 | optional uint32 slot_id = 4 [(key_field) = true]; 455 | } 456 | 457 | message CMsgGCToGCDirtySDOCache { 458 | optional uint32 sdo_type = 1; 459 | optional uint64 key_uint64 = 2; 460 | } 461 | 462 | message CMsgGCToGCDirtyMultipleSDOCache { 463 | optional uint32 sdo_type = 1; 464 | repeated uint64 key_uint64 = 2; 465 | } 466 | 467 | message CMsgGCCollectItem { 468 | optional uint64 collection_item_id = 1; 469 | optional uint64 subject_item_id = 2; 470 | } 471 | 472 | message CMsgSDONoMemcached { 473 | } 474 | 475 | message CMsgGCToGCUpdateSQLKeyValue { 476 | optional string key_name = 1; 477 | } 478 | 479 | message CMsgGCToGCIsTrustedServer { 480 | optional fixed64 steam_id = 1; 481 | } 482 | 483 | message CMsgGCToGCIsTrustedServerResponse { 484 | optional bool is_trusted = 1; 485 | } 486 | 487 | message CMsgGCToGCBroadcastConsoleCommand { 488 | optional string con_command = 1; 489 | } 490 | 491 | message CMsgGCServerVersionUpdated { 492 | optional uint32 server_version = 1; 493 | } 494 | 495 | message CMsgGCClientVersionUpdated { 496 | optional uint32 client_version = 1; 497 | } 498 | 499 | message CMsgGCToGCWebAPIAccountChanged { 500 | } 501 | 502 | message CMsgGCToGCRequestPassportItemGrant { 503 | optional fixed64 steam_id = 1; 504 | optional uint32 league_id = 2; 505 | optional int32 reward_flag = 3; 506 | } 507 | 508 | message CMsgGameServerInfo { 509 | enum ServerType { 510 | UNSPECIFIED = 0; 511 | GAME = 1; 512 | PROXY = 2; 513 | } 514 | 515 | optional fixed32 server_public_ip_addr = 1; 516 | optional fixed32 server_private_ip_addr = 2; 517 | optional uint32 server_port = 3; 518 | optional uint32 server_tv_port = 4; 519 | optional string server_key = 5; 520 | optional bool server_hibernation = 6; 521 | optional .CMsgGameServerInfo.ServerType server_type = 7 [default = UNSPECIFIED]; 522 | optional uint32 server_region = 8; 523 | optional float server_loadavg = 9; 524 | optional float server_tv_broadcast_time = 10; 525 | optional float server_game_time = 11; 526 | optional fixed64 server_relay_connected_steam_id = 12; 527 | optional uint32 relay_slots_max = 13; 528 | optional int32 relays_connected = 14; 529 | optional int32 relay_clients_connected = 15; 530 | optional fixed64 relayed_game_server_steam_id = 16; 531 | optional uint32 parent_relay_count = 17; 532 | optional fixed64 tv_secret_code = 18; 533 | } -------------------------------------------------------------------------------- /protos/engine_gcmessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | option cc_generic_services = false; 4 | 5 | message CEngineGotvSyncPacket { 6 | optional uint64 match_id = 1; 7 | optional uint32 instance_id = 2; 8 | optional uint32 signupfragment = 3; 9 | optional uint32 currentfragment = 4; 10 | optional float tickrate = 5; 11 | optional uint32 tick = 6; 12 | optional float rtdelay = 8; 13 | optional float rcvage = 9; 14 | optional float keyframe_interval = 10; 15 | optional uint32 cdndelay = 11; 16 | } -------------------------------------------------------------------------------- /protos/gcsystemmsgs.proto: -------------------------------------------------------------------------------- 1 | option optimize_for = SPEED; 2 | option cc_generic_services = false; 3 | 4 | enum EGCSystemMsg { 5 | k_EGCMsgInvalid = 0; 6 | k_EGCMsgMulti = 1; 7 | k_EGCMsgGenericReply = 10; 8 | k_EGCMsgSystemBase = 50; 9 | k_EGCMsgAchievementAwarded = 51; 10 | k_EGCMsgConCommand = 52; 11 | k_EGCMsgStartPlaying = 53; 12 | k_EGCMsgStopPlaying = 54; 13 | k_EGCMsgStartGameserver = 55; 14 | k_EGCMsgStopGameserver = 56; 15 | k_EGCMsgWGRequest = 57; 16 | k_EGCMsgWGResponse = 58; 17 | k_EGCMsgGetUserGameStatsSchema = 59; 18 | k_EGCMsgGetUserGameStatsSchemaResponse = 60; 19 | k_EGCMsgGetUserStatsDEPRECATED = 61; 20 | k_EGCMsgGetUserStatsResponse = 62; 21 | k_EGCMsgAppInfoUpdated = 63; 22 | k_EGCMsgValidateSession = 64; 23 | k_EGCMsgValidateSessionResponse = 65; 24 | k_EGCMsgLookupAccountFromInput = 66; 25 | k_EGCMsgSendHTTPRequest = 67; 26 | k_EGCMsgSendHTTPRequestResponse = 68; 27 | k_EGCMsgPreTestSetup = 69; 28 | k_EGCMsgRecordSupportAction = 70; 29 | k_EGCMsgGetAccountDetails_DEPRECATED = 71; 30 | k_EGCMsgReceiveInterAppMessage = 73; 31 | k_EGCMsgFindAccounts = 74; 32 | k_EGCMsgPostAlert = 75; 33 | k_EGCMsgGetLicenses = 76; 34 | k_EGCMsgGetUserStats = 77; 35 | k_EGCMsgGetCommands = 78; 36 | k_EGCMsgGetCommandsResponse = 79; 37 | k_EGCMsgAddFreeLicense = 80; 38 | k_EGCMsgAddFreeLicenseResponse = 81; 39 | k_EGCMsgGetIPLocation = 82; 40 | k_EGCMsgGetIPLocationResponse = 83; 41 | k_EGCMsgSystemStatsSchema = 84; 42 | k_EGCMsgGetSystemStats = 85; 43 | k_EGCMsgGetSystemStatsResponse = 86; 44 | k_EGCMsgSendEmail = 87; 45 | k_EGCMsgSendEmailResponse = 88; 46 | k_EGCMsgGetEmailTemplate = 89; 47 | k_EGCMsgGetEmailTemplateResponse = 90; 48 | k_EGCMsgGrantGuestPass = 91; 49 | k_EGCMsgGrantGuestPassResponse = 92; 50 | k_EGCMsgGetAccountDetails = 93; 51 | k_EGCMsgGetAccountDetailsResponse = 94; 52 | k_EGCMsgGetPersonaNames = 95; 53 | k_EGCMsgGetPersonaNamesResponse = 96; 54 | k_EGCMsgMultiplexMsg = 97; 55 | k_EGCMsgMultiplexMsgResponse = 98; 56 | k_EGCMsgWebAPIRegisterInterfaces = 101; 57 | k_EGCMsgWebAPIJobRequest = 102; 58 | k_EGCMsgWebAPIJobRequestHttpResponse = 104; 59 | k_EGCMsgWebAPIJobRequestForwardResponse = 105; 60 | k_EGCMsgMemCachedGet = 200; 61 | k_EGCMsgMemCachedGetResponse = 201; 62 | k_EGCMsgMemCachedSet = 202; 63 | k_EGCMsgMemCachedDelete = 203; 64 | k_EGCMsgMemCachedStats = 204; 65 | k_EGCMsgMemCachedStatsResponse = 205; 66 | k_EGCMsgMasterSetDirectory = 220; 67 | k_EGCMsgMasterSetDirectoryResponse = 221; 68 | k_EGCMsgMasterSetWebAPIRouting = 222; 69 | k_EGCMsgMasterSetWebAPIRoutingResponse = 223; 70 | k_EGCMsgMasterSetClientMsgRouting = 224; 71 | k_EGCMsgMasterSetClientMsgRoutingResponse = 225; 72 | k_EGCMsgSetOptions = 226; 73 | k_EGCMsgSetOptionsResponse = 227; 74 | k_EGCMsgSystemBase2 = 500; 75 | k_EGCMsgGetPurchaseTrustStatus = 501; 76 | k_EGCMsgGetPurchaseTrustStatusResponse = 502; 77 | k_EGCMsgUpdateSession = 503; 78 | k_EGCMsgGCAccountVacStatusChange = 504; 79 | k_EGCMsgCheckFriendship = 505; 80 | k_EGCMsgCheckFriendshipResponse = 506; 81 | k_EGCMsgGetPartnerAccountLink = 507; 82 | k_EGCMsgGetPartnerAccountLinkResponse = 508; 83 | k_EGCMsgDPPartnerMicroTxns = 512; 84 | k_EGCMsgDPPartnerMicroTxnsResponse = 513; 85 | k_EGCMsgVacVerificationChange = 518; 86 | k_EGCMsgAccountPhoneNumberChange = 519; 87 | k_EGCMsgInviteUserToLobby = 523; 88 | k_EGCMsgGetGamePersonalDataCategoriesRequest = 524; 89 | k_EGCMsgGetGamePersonalDataCategoriesResponse = 525; 90 | k_EGCMsgGetGamePersonalDataEntriesRequest = 526; 91 | k_EGCMsgGetGamePersonalDataEntriesResponse = 527; 92 | k_EGCMsgTerminateGamePersonalDataEntriesRequest = 528; 93 | k_EGCMsgTerminateGamePersonalDataEntriesResponse = 529; 94 | } 95 | 96 | enum ESOMsg { 97 | k_ESOMsg_Create = 21; 98 | k_ESOMsg_Update = 22; 99 | k_ESOMsg_Destroy = 23; 100 | k_ESOMsg_CacheSubscribed = 24; 101 | k_ESOMsg_CacheUnsubscribed = 25; 102 | k_ESOMsg_UpdateMultiple = 26; 103 | k_ESOMsg_CacheSubscriptionCheck = 27; 104 | k_ESOMsg_CacheSubscriptionRefresh = 28; 105 | } 106 | 107 | enum EGCBaseClientMsg { 108 | k_EMsgGCClientWelcome = 4004; 109 | k_EMsgGCServerWelcome = 4005; 110 | k_EMsgGCClientHello = 4006; 111 | k_EMsgGCServerHello = 4007; 112 | k_EMsgGCClientConnectionStatus = 4009; 113 | k_EMsgGCServerConnectionStatus = 4010; 114 | k_EMsgGCClientHelloPartner = 4011; 115 | k_EMsgGCClientHelloPW = 4012; 116 | k_EMsgGCClientHelloR2 = 4013; 117 | k_EMsgGCClientHelloR3 = 4014; 118 | k_EMsgGCClientHelloR4 = 4015; 119 | } 120 | 121 | enum EGCToGCMsg { 122 | k_EGCToGCMsgMasterAck = 150; 123 | k_EGCToGCMsgMasterAckResponse = 151; 124 | k_EGCToGCMsgRouted = 152; 125 | k_EGCToGCMsgRoutedReply = 153; 126 | k_EMsgUpdateSessionIP = 154; 127 | k_EMsgRequestSessionIP = 155; 128 | k_EMsgRequestSessionIPResponse = 156; 129 | k_EGCToGCMsgMasterStartupComplete = 157; 130 | } 131 | 132 | enum ECommunityItemClass { 133 | k_ECommunityItemClass_Invalid = 0; 134 | k_ECommunityItemClass_Badge = 1; 135 | k_ECommunityItemClass_GameCard = 2; 136 | k_ECommunityItemClass_ProfileBackground = 3; 137 | k_ECommunityItemClass_Emoticon = 4; 138 | k_ECommunityItemClass_BoosterPack = 5; 139 | k_ECommunityItemClass_Consumable = 6; 140 | k_ECommunityItemClass_GameGoo = 7; 141 | k_ECommunityItemClass_ProfileModifier = 8; 142 | k_ECommunityItemClass_Scene = 9; 143 | k_ECommunityItemClass_SalienItem = 10; 144 | } 145 | 146 | enum ECommunityItemAttribute { 147 | k_ECommunityItemAttribute_Invalid = 0; 148 | k_ECommunityItemAttribute_CardBorder = 1; 149 | k_ECommunityItemAttribute_Level = 2; 150 | k_ECommunityItemAttribute_IssueNumber = 3; 151 | k_ECommunityItemAttribute_TradableTime = 4; 152 | k_ECommunityItemAttribute_StorePackageID = 5; 153 | k_ECommunityItemAttribute_CommunityItemAppID = 6; 154 | k_ECommunityItemAttribute_CommunityItemType = 7; 155 | k_ECommunityItemAttribute_ProfileModiferEnabled = 8; 156 | k_ECommunityItemAttribute_ExpiryTime = 9; 157 | } 158 | 159 | message CMsgGCHVacVerificationChange { 160 | optional fixed64 steamid = 1; 161 | optional uint32 appid = 2; 162 | optional bool is_verified = 3; 163 | } 164 | 165 | message CMsgGCHAccountPhoneNumberChange { 166 | optional fixed64 steamid = 1; 167 | optional uint32 appid = 2; 168 | optional uint64 phone_id = 3; 169 | optional bool is_verified = 4; 170 | optional bool is_identifying = 5; 171 | } 172 | 173 | message CMsgGCHInviteUserToLobby { 174 | optional fixed64 steamid = 1; 175 | optional uint32 appid = 2; 176 | optional fixed64 steamid_invited = 3; 177 | optional fixed64 steamid_lobby = 4; 178 | } 179 | 180 | message CQuest_PublisherAddCommunityItemsToPlayer_Request { 181 | message Attribute { 182 | optional uint32 attribute = 1; 183 | optional uint64 value = 2; 184 | } 185 | 186 | optional uint64 steamid = 1; 187 | optional uint32 appid = 2; 188 | optional uint32 match_item_type = 3; 189 | optional uint32 match_item_class = 4; 190 | optional string prefix_item_name = 5; 191 | repeated .CQuest_PublisherAddCommunityItemsToPlayer_Request.Attribute attributes = 6; 192 | optional string note = 7; 193 | } 194 | 195 | message CQuest_PublisherAddCommunityItemsToPlayer_Response { 196 | optional uint32 items_matched = 1; 197 | optional uint32 items_granted = 2; 198 | } 199 | 200 | message CCommunity_GamePersonalDataCategoryInfo { 201 | optional string type = 1; 202 | optional string localization_token = 2; 203 | optional string template_file = 3; 204 | } 205 | 206 | message CCommunity_GetGamePersonalDataCategories_Request { 207 | optional uint32 appid = 1; 208 | } 209 | 210 | message CCommunity_GetGamePersonalDataCategories_Response { 211 | repeated .CCommunity_GamePersonalDataCategoryInfo categories = 1; 212 | optional string app_assets_basename = 2; 213 | } 214 | 215 | message CCommunity_GetGamePersonalDataEntries_Request { 216 | optional uint32 appid = 1; 217 | optional uint64 steamid = 2; 218 | optional string type = 3; 219 | optional string continue_token = 4; 220 | } 221 | 222 | message CCommunity_GetGamePersonalDataEntries_Response { 223 | optional uint32 gceresult = 1; 224 | repeated string entries = 2; 225 | optional string continue_token = 3; 226 | } 227 | 228 | message CCommunity_TerminateGamePersonalDataEntries_Request { 229 | optional uint32 appid = 1; 230 | optional uint64 steamid = 2; 231 | } 232 | 233 | message CCommunity_TerminateGamePersonalDataEntries_Response { 234 | optional uint32 gceresult = 1; 235 | } -------------------------------------------------------------------------------- /protos/google/protobuf/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "github.com/golang/protobuf/ptypes/any"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := ptypes.MarshalAny(foo) 81 | // ... 82 | // foo := &pb.Foo{} 83 | // if err := ptypes.UnmarshalAny(any, foo); err != nil { 84 | // ... 85 | // } 86 | // 87 | // The pack methods provided by protobuf library will by default use 88 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 89 | // methods only use the fully qualified type name after the last '/' 90 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 91 | // name "y.z". 92 | // 93 | // 94 | // JSON 95 | // ==== 96 | // The JSON representation of an `Any` value uses the regular 97 | // representation of the deserialized, embedded message, with an 98 | // additional field `@type` which contains the type URL. Example: 99 | // 100 | // package google.profile; 101 | // message Person { 102 | // string first_name = 1; 103 | // string last_name = 2; 104 | // } 105 | // 106 | // { 107 | // "@type": "type.googleapis.com/google.profile.Person", 108 | // "firstName": , 109 | // "lastName": 110 | // } 111 | // 112 | // If the embedded message type is well-known and has a custom JSON 113 | // representation, that representation will be embedded adding a field 114 | // `value` which holds the custom JSON in addition to the `@type` 115 | // field. Example (for message [google.protobuf.Duration][]): 116 | // 117 | // { 118 | // "@type": "type.googleapis.com/google.protobuf.Duration", 119 | // "value": "1.212s" 120 | // } 121 | // 122 | message Any { 123 | // A URL/resource name that uniquely identifies the type of the serialized 124 | // protocol buffer message. The last segment of the URL's path must represent 125 | // the fully qualified name of the type (as in 126 | // `path/google.protobuf.Duration`). The name should be in a canonical form 127 | // (e.g., leading "." is not accepted). 128 | // 129 | // In practice, teams usually precompile into the binary all types that they 130 | // expect it to use in the context of Any. However, for URLs which use the 131 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 132 | // server that maps type URLs to message definitions as follows: 133 | // 134 | // * If no scheme is provided, `https` is assumed. 135 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 136 | // value in binary format, or produce an error. 137 | // * Applications are allowed to cache lookup results based on the 138 | // URL, or have them precompiled into a binary to avoid any 139 | // lookup. Therefore, binary compatibility needs to be preserved 140 | // on changes to types. (Use versioned type names to manage 141 | // breaking changes.) 142 | // 143 | // Note: this functionality is not currently available in the official 144 | // protobuf release, and it is not used for type URLs beginning with 145 | // type.googleapis.com. 146 | // 147 | // Schemes other than `http`, `https` (or the empty scheme) might be 148 | // used with implementation specific semantics. 149 | // 150 | string type_url = 1; 151 | 152 | // Must be a valid serialized protocol buffer of the above specified type. 153 | bytes value = 2; 154 | } 155 | -------------------------------------------------------------------------------- /protos/google/protobuf/api.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/source_context.proto"; 36 | import "google/protobuf/type.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option java_package = "com.google.protobuf"; 40 | option java_outer_classname = "ApiProto"; 41 | option java_multiple_files = true; 42 | option objc_class_prefix = "GPB"; 43 | option go_package = "google.golang.org/genproto/protobuf/api;api"; 44 | 45 | // Api is a light-weight descriptor for an API Interface. 46 | // 47 | // Interfaces are also described as "protocol buffer services" in some contexts, 48 | // such as by the "service" keyword in a .proto file, but they are different 49 | // from API Services, which represent a concrete implementation of an interface 50 | // as opposed to simply a description of methods and bindings. They are also 51 | // sometimes simply referred to as "APIs" in other contexts, such as the name of 52 | // this message itself. See https://cloud.google.com/apis/design/glossary for 53 | // detailed terminology. 54 | message Api { 55 | 56 | // The fully qualified name of this interface, including package name 57 | // followed by the interface's simple name. 58 | string name = 1; 59 | 60 | // The methods of this interface, in unspecified order. 61 | repeated Method methods = 2; 62 | 63 | // Any metadata attached to the interface. 64 | repeated Option options = 3; 65 | 66 | // A version string for this interface. If specified, must have the form 67 | // `major-version.minor-version`, as in `1.10`. If the minor version is 68 | // omitted, it defaults to zero. If the entire version field is empty, the 69 | // major version is derived from the package name, as outlined below. If the 70 | // field is not empty, the version in the package name will be verified to be 71 | // consistent with what is provided here. 72 | // 73 | // The versioning schema uses [semantic 74 | // versioning](http://semver.org) where the major version number 75 | // indicates a breaking change and the minor version an additive, 76 | // non-breaking change. Both version numbers are signals to users 77 | // what to expect from different versions, and should be carefully 78 | // chosen based on the product plan. 79 | // 80 | // The major version is also reflected in the package name of the 81 | // interface, which must end in `v`, as in 82 | // `google.feature.v1`. For major versions 0 and 1, the suffix can 83 | // be omitted. Zero major versions must only be used for 84 | // experimental, non-GA interfaces. 85 | // 86 | // 87 | string version = 4; 88 | 89 | // Source context for the protocol buffer service represented by this 90 | // message. 91 | SourceContext source_context = 5; 92 | 93 | // Included interfaces. See [Mixin][]. 94 | repeated Mixin mixins = 6; 95 | 96 | // The source syntax of the service. 97 | Syntax syntax = 7; 98 | } 99 | 100 | // Method represents a method of an API interface. 101 | message Method { 102 | 103 | // The simple name of this method. 104 | string name = 1; 105 | 106 | // A URL of the input message type. 107 | string request_type_url = 2; 108 | 109 | // If true, the request is streamed. 110 | bool request_streaming = 3; 111 | 112 | // The URL of the output message type. 113 | string response_type_url = 4; 114 | 115 | // If true, the response is streamed. 116 | bool response_streaming = 5; 117 | 118 | // Any metadata attached to the method. 119 | repeated Option options = 6; 120 | 121 | // The source syntax of this method. 122 | Syntax syntax = 7; 123 | } 124 | 125 | // Declares an API Interface to be included in this interface. The including 126 | // interface must redeclare all the methods from the included interface, but 127 | // documentation and options are inherited as follows: 128 | // 129 | // - If after comment and whitespace stripping, the documentation 130 | // string of the redeclared method is empty, it will be inherited 131 | // from the original method. 132 | // 133 | // - Each annotation belonging to the service config (http, 134 | // visibility) which is not set in the redeclared method will be 135 | // inherited. 136 | // 137 | // - If an http annotation is inherited, the path pattern will be 138 | // modified as follows. Any version prefix will be replaced by the 139 | // version of the including interface plus the [root][] path if 140 | // specified. 141 | // 142 | // Example of a simple mixin: 143 | // 144 | // package google.acl.v1; 145 | // service AccessControl { 146 | // // Get the underlying ACL object. 147 | // rpc GetAcl(GetAclRequest) returns (Acl) { 148 | // option (google.api.http).get = "/v1/{resource=**}:getAcl"; 149 | // } 150 | // } 151 | // 152 | // package google.storage.v2; 153 | // service Storage { 154 | // rpc GetAcl(GetAclRequest) returns (Acl); 155 | // 156 | // // Get a data record. 157 | // rpc GetData(GetDataRequest) returns (Data) { 158 | // option (google.api.http).get = "/v2/{resource=**}"; 159 | // } 160 | // } 161 | // 162 | // Example of a mixin configuration: 163 | // 164 | // apis: 165 | // - name: google.storage.v2.Storage 166 | // mixins: 167 | // - name: google.acl.v1.AccessControl 168 | // 169 | // The mixin construct implies that all methods in `AccessControl` are 170 | // also declared with same name and request/response types in 171 | // `Storage`. A documentation generator or annotation processor will 172 | // see the effective `Storage.GetAcl` method after inherting 173 | // documentation and annotations as follows: 174 | // 175 | // service Storage { 176 | // // Get the underlying ACL object. 177 | // rpc GetAcl(GetAclRequest) returns (Acl) { 178 | // option (google.api.http).get = "/v2/{resource=**}:getAcl"; 179 | // } 180 | // ... 181 | // } 182 | // 183 | // Note how the version in the path pattern changed from `v1` to `v2`. 184 | // 185 | // If the `root` field in the mixin is specified, it should be a 186 | // relative path under which inherited HTTP paths are placed. Example: 187 | // 188 | // apis: 189 | // - name: google.storage.v2.Storage 190 | // mixins: 191 | // - name: google.acl.v1.AccessControl 192 | // root: acls 193 | // 194 | // This implies the following inherited HTTP annotation: 195 | // 196 | // service Storage { 197 | // // Get the underlying ACL object. 198 | // rpc GetAcl(GetAclRequest) returns (Acl) { 199 | // option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; 200 | // } 201 | // ... 202 | // } 203 | message Mixin { 204 | // The fully qualified name of the interface which is included. 205 | string name = 1; 206 | 207 | // If non-empty specifies a path under which inherited HTTP paths 208 | // are rooted. 209 | string root = 2; 210 | } 211 | -------------------------------------------------------------------------------- /protos/google/protobuf/duration.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "github.com/golang/protobuf/ptypes/duration"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "DurationProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Duration represents a signed, fixed-length span of time represented 44 | // as a count of seconds and fractions of seconds at nanosecond 45 | // resolution. It is independent of any calendar and concepts like "day" 46 | // or "month". It is related to Timestamp in that the difference between 47 | // two Timestamp values is a Duration and it can be added or subtracted 48 | // from a Timestamp. Range is approximately +-10,000 years. 49 | // 50 | // # Examples 51 | // 52 | // Example 1: Compute Duration from two Timestamps in pseudo code. 53 | // 54 | // Timestamp start = ...; 55 | // Timestamp end = ...; 56 | // Duration duration = ...; 57 | // 58 | // duration.seconds = end.seconds - start.seconds; 59 | // duration.nanos = end.nanos - start.nanos; 60 | // 61 | // if (duration.seconds < 0 && duration.nanos > 0) { 62 | // duration.seconds += 1; 63 | // duration.nanos -= 1000000000; 64 | // } else if (durations.seconds > 0 && duration.nanos < 0) { 65 | // duration.seconds -= 1; 66 | // duration.nanos += 1000000000; 67 | // } 68 | // 69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 70 | // 71 | // Timestamp start = ...; 72 | // Duration duration = ...; 73 | // Timestamp end = ...; 74 | // 75 | // end.seconds = start.seconds + duration.seconds; 76 | // end.nanos = start.nanos + duration.nanos; 77 | // 78 | // if (end.nanos < 0) { 79 | // end.seconds -= 1; 80 | // end.nanos += 1000000000; 81 | // } else if (end.nanos >= 1000000000) { 82 | // end.seconds += 1; 83 | // end.nanos -= 1000000000; 84 | // } 85 | // 86 | // Example 3: Compute Duration from datetime.timedelta in Python. 87 | // 88 | // td = datetime.timedelta(days=3, minutes=10) 89 | // duration = Duration() 90 | // duration.FromTimedelta(td) 91 | // 92 | // # JSON Mapping 93 | // 94 | // In JSON format, the Duration type is encoded as a string rather than an 95 | // object, where the string ends in the suffix "s" (indicating seconds) and 96 | // is preceded by the number of seconds, with nanoseconds expressed as 97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be 98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 100 | // microsecond should be expressed in JSON format as "3.000001s". 101 | // 102 | // 103 | message Duration { 104 | 105 | // Signed seconds of the span of time. Must be from -315,576,000,000 106 | // to +315,576,000,000 inclusive. Note: these bounds are computed from: 107 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 108 | int64 seconds = 1; 109 | 110 | // Signed fractions of a second at nanosecond resolution of the span 111 | // of time. Durations less than one second are represented with a 0 112 | // `seconds` field and a positive or negative `nanos` field. For durations 113 | // of one second or more, a non-zero value for the `nanos` field must be 114 | // of the same sign as the `seconds` field. Must be from -999,999,999 115 | // to +999,999,999 inclusive. 116 | int32 nanos = 2; 117 | } 118 | -------------------------------------------------------------------------------- /protos/google/protobuf/empty.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "github.com/golang/protobuf/ptypes/empty"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "EmptyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | option cc_enable_arenas = true; 42 | 43 | // A generic empty message that you can re-use to avoid defining duplicated 44 | // empty messages in your APIs. A typical example is to use it as the request 45 | // or the response type of an API method. For instance: 46 | // 47 | // service Foo { 48 | // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 49 | // } 50 | // 51 | // The JSON representation for `Empty` is empty JSON object `{}`. 52 | message Empty {} 53 | -------------------------------------------------------------------------------- /protos/google/protobuf/field_mask.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "FieldMaskProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/genproto/protobuf/field_mask;field_mask"; 41 | 42 | // `FieldMask` represents a set of symbolic field paths, for example: 43 | // 44 | // paths: "f.a" 45 | // paths: "f.b.d" 46 | // 47 | // Here `f` represents a field in some root message, `a` and `b` 48 | // fields in the message found in `f`, and `d` a field found in the 49 | // message in `f.b`. 50 | // 51 | // Field masks are used to specify a subset of fields that should be 52 | // returned by a get operation or modified by an update operation. 53 | // Field masks also have a custom JSON encoding (see below). 54 | // 55 | // # Field Masks in Projections 56 | // 57 | // When used in the context of a projection, a response message or 58 | // sub-message is filtered by the API to only contain those fields as 59 | // specified in the mask. For example, if the mask in the previous 60 | // example is applied to a response message as follows: 61 | // 62 | // f { 63 | // a : 22 64 | // b { 65 | // d : 1 66 | // x : 2 67 | // } 68 | // y : 13 69 | // } 70 | // z: 8 71 | // 72 | // The result will not contain specific values for fields x,y and z 73 | // (their value will be set to the default, and omitted in proto text 74 | // output): 75 | // 76 | // 77 | // f { 78 | // a : 22 79 | // b { 80 | // d : 1 81 | // } 82 | // } 83 | // 84 | // A repeated field is not allowed except at the last position of a 85 | // paths string. 86 | // 87 | // If a FieldMask object is not present in a get operation, the 88 | // operation applies to all fields (as if a FieldMask of all fields 89 | // had been specified). 90 | // 91 | // Note that a field mask does not necessarily apply to the 92 | // top-level response message. In case of a REST get operation, the 93 | // field mask applies directly to the response, but in case of a REST 94 | // list operation, the mask instead applies to each individual message 95 | // in the returned resource list. In case of a REST custom method, 96 | // other definitions may be used. Where the mask applies will be 97 | // clearly documented together with its declaration in the API. In 98 | // any case, the effect on the returned resource/resources is required 99 | // behavior for APIs. 100 | // 101 | // # Field Masks in Update Operations 102 | // 103 | // A field mask in update operations specifies which fields of the 104 | // targeted resource are going to be updated. The API is required 105 | // to only change the values of the fields as specified in the mask 106 | // and leave the others untouched. If a resource is passed in to 107 | // describe the updated values, the API ignores the values of all 108 | // fields not covered by the mask. 109 | // 110 | // If a repeated field is specified for an update operation, the existing 111 | // repeated values in the target resource will be overwritten by the new values. 112 | // Note that a repeated field is only allowed in the last position of a `paths` 113 | // string. 114 | // 115 | // If a sub-message is specified in the last position of the field mask for an 116 | // update operation, then the existing sub-message in the target resource is 117 | // overwritten. Given the target message: 118 | // 119 | // f { 120 | // b { 121 | // d : 1 122 | // x : 2 123 | // } 124 | // c : 1 125 | // } 126 | // 127 | // And an update message: 128 | // 129 | // f { 130 | // b { 131 | // d : 10 132 | // } 133 | // } 134 | // 135 | // then if the field mask is: 136 | // 137 | // paths: "f.b" 138 | // 139 | // then the result will be: 140 | // 141 | // f { 142 | // b { 143 | // d : 10 144 | // } 145 | // c : 1 146 | // } 147 | // 148 | // However, if the update mask was: 149 | // 150 | // paths: "f.b.d" 151 | // 152 | // then the result would be: 153 | // 154 | // f { 155 | // b { 156 | // d : 10 157 | // x : 2 158 | // } 159 | // c : 1 160 | // } 161 | // 162 | // In order to reset a field's value to the default, the field must 163 | // be in the mask and set to the default value in the provided resource. 164 | // Hence, in order to reset all fields of a resource, provide a default 165 | // instance of the resource and set all fields in the mask, or do 166 | // not provide a mask as described below. 167 | // 168 | // If a field mask is not present on update, the operation applies to 169 | // all fields (as if a field mask of all fields has been specified). 170 | // Note that in the presence of schema evolution, this may mean that 171 | // fields the client does not know and has therefore not filled into 172 | // the request will be reset to their default. If this is unwanted 173 | // behavior, a specific service may require a client to always specify 174 | // a field mask, producing an error if not. 175 | // 176 | // As with get operations, the location of the resource which 177 | // describes the updated values in the request message depends on the 178 | // operation kind. In any case, the effect of the field mask is 179 | // required to be honored by the API. 180 | // 181 | // ## Considerations for HTTP REST 182 | // 183 | // The HTTP kind of an update operation which uses a field mask must 184 | // be set to PATCH instead of PUT in order to satisfy HTTP semantics 185 | // (PUT must only be used for full updates). 186 | // 187 | // # JSON Encoding of Field Masks 188 | // 189 | // In JSON, a field mask is encoded as a single string where paths are 190 | // separated by a comma. Fields name in each path are converted 191 | // to/from lower-camel naming conventions. 192 | // 193 | // As an example, consider the following message declarations: 194 | // 195 | // message Profile { 196 | // User user = 1; 197 | // Photo photo = 2; 198 | // } 199 | // message User { 200 | // string display_name = 1; 201 | // string address = 2; 202 | // } 203 | // 204 | // In proto a field mask for `Profile` may look as such: 205 | // 206 | // mask { 207 | // paths: "user.display_name" 208 | // paths: "photo" 209 | // } 210 | // 211 | // In JSON, the same mask is represented as below: 212 | // 213 | // { 214 | // mask: "user.displayName,photo" 215 | // } 216 | // 217 | // # Field Masks and Oneof Fields 218 | // 219 | // Field masks treat fields in oneofs just as regular fields. Consider the 220 | // following message: 221 | // 222 | // message SampleMessage { 223 | // oneof test_oneof { 224 | // string name = 4; 225 | // SubMessage sub_message = 9; 226 | // } 227 | // } 228 | // 229 | // The field mask can be: 230 | // 231 | // mask { 232 | // paths: "name" 233 | // } 234 | // 235 | // Or: 236 | // 237 | // mask { 238 | // paths: "sub_message" 239 | // } 240 | // 241 | // Note that oneof type names ("test_oneof" in this case) cannot be used in 242 | // paths. 243 | // 244 | // ## Field Mask Verification 245 | // 246 | // The implementation of any API method which has a FieldMask type field in the 247 | // request should verify the included field paths, and return an 248 | // `INVALID_ARGUMENT` error if any path is duplicated or unmappable. 249 | message FieldMask { 250 | // The set of field mask paths. 251 | repeated string paths = 1; 252 | } 253 | -------------------------------------------------------------------------------- /protos/google/protobuf/source_context.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "SourceContextProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/genproto/protobuf/source_context;source_context"; 41 | 42 | // `SourceContext` represents information about the source of a 43 | // protobuf element, like the file in which it is defined. 44 | message SourceContext { 45 | // The path-qualified name of the .proto file that contained the associated 46 | // protobuf element. For example: `"google/protobuf/source_context.proto"`. 47 | string file_name = 1; 48 | } 49 | -------------------------------------------------------------------------------- /protos/google/protobuf/struct.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "github.com/golang/protobuf/ptypes/struct;structpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "StructProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | 44 | // `Struct` represents a structured data value, consisting of fields 45 | // which map to dynamically typed values. In some languages, `Struct` 46 | // might be supported by a native representation. For example, in 47 | // scripting languages like JS a struct is represented as an 48 | // object. The details of that representation are described together 49 | // with the proto support for the language. 50 | // 51 | // The JSON representation for `Struct` is JSON object. 52 | message Struct { 53 | // Unordered map of dynamically typed values. 54 | map fields = 1; 55 | } 56 | 57 | // `Value` represents a dynamically typed value which can be either 58 | // null, a number, a string, a boolean, a recursive struct value, or a 59 | // list of values. A producer of value is expected to set one of that 60 | // variants, absence of any variant indicates an error. 61 | // 62 | // The JSON representation for `Value` is JSON value. 63 | message Value { 64 | // The kind of value. 65 | oneof kind { 66 | // Represents a null value. 67 | NullValue null_value = 1; 68 | // Represents a double value. 69 | double number_value = 2; 70 | // Represents a string value. 71 | string string_value = 3; 72 | // Represents a boolean value. 73 | bool bool_value = 4; 74 | // Represents a structured value. 75 | Struct struct_value = 5; 76 | // Represents a repeated `Value`. 77 | ListValue list_value = 6; 78 | } 79 | } 80 | 81 | // `NullValue` is a singleton enumeration to represent the null value for the 82 | // `Value` type union. 83 | // 84 | // The JSON representation for `NullValue` is JSON `null`. 85 | enum NullValue { 86 | // Null value. 87 | NULL_VALUE = 0; 88 | } 89 | 90 | // `ListValue` is a wrapper around a repeated field of values. 91 | // 92 | // The JSON representation for `ListValue` is JSON array. 93 | message ListValue { 94 | // Repeated field of dynamically typed values. 95 | repeated Value values = 1; 96 | } 97 | -------------------------------------------------------------------------------- /protos/google/protobuf/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "github.com/golang/protobuf/ptypes/timestamp"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "TimestampProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Timestamp represents a point in time independent of any time zone 44 | // or calendar, represented as seconds and fractions of seconds at 45 | // nanosecond resolution in UTC Epoch time. It is encoded using the 46 | // Proleptic Gregorian Calendar which extends the Gregorian calendar 47 | // backwards to year one. It is encoded assuming all minutes are 60 48 | // seconds long, i.e. leap seconds are "smeared" so that no leap second 49 | // table is needed for interpretation. Range is from 50 | // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. 51 | // By restricting to that range, we ensure that we can convert to 52 | // and from RFC 3339 date strings. 53 | // See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). 54 | // 55 | // # Examples 56 | // 57 | // Example 1: Compute Timestamp from POSIX `time()`. 58 | // 59 | // Timestamp timestamp; 60 | // timestamp.set_seconds(time(NULL)); 61 | // timestamp.set_nanos(0); 62 | // 63 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 64 | // 65 | // struct timeval tv; 66 | // gettimeofday(&tv, NULL); 67 | // 68 | // Timestamp timestamp; 69 | // timestamp.set_seconds(tv.tv_sec); 70 | // timestamp.set_nanos(tv.tv_usec * 1000); 71 | // 72 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 73 | // 74 | // FILETIME ft; 75 | // GetSystemTimeAsFileTime(&ft); 76 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 77 | // 78 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 79 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 80 | // Timestamp timestamp; 81 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 82 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 83 | // 84 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 85 | // 86 | // long millis = System.currentTimeMillis(); 87 | // 88 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 89 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 90 | // 91 | // 92 | // Example 5: Compute Timestamp from current time in Python. 93 | // 94 | // timestamp = Timestamp() 95 | // timestamp.GetCurrentTime() 96 | // 97 | // # JSON Mapping 98 | // 99 | // In JSON format, the Timestamp type is encoded as a string in the 100 | // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 101 | // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 102 | // where {year} is always expressed using four digits while {month}, {day}, 103 | // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 104 | // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 105 | // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 106 | // is required. A proto3 JSON serializer should always use UTC (as indicated by 107 | // "Z") when printing the Timestamp type and a proto3 JSON parser should be 108 | // able to accept both UTC and other timezones (as indicated by an offset). 109 | // 110 | // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 111 | // 01:30 UTC on January 15, 2017. 112 | // 113 | // In JavaScript, one can convert a Date object to this format using the 114 | // standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString] 115 | // method. In Python, a standard `datetime.datetime` object can be converted 116 | // to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) 117 | // with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one 118 | // can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( 119 | // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- 120 | // ) to obtain a formatter capable of generating timestamps in this format. 121 | // 122 | // 123 | message Timestamp { 124 | 125 | // Represents seconds of UTC time since Unix epoch 126 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 127 | // 9999-12-31T23:59:59Z inclusive. 128 | int64 seconds = 1; 129 | 130 | // Non-negative fractions of a second at nanosecond resolution. Negative 131 | // second values with fractions must still have non-negative nanos values 132 | // that count forward in time. Must be from 0 to 999,999,999 133 | // inclusive. 134 | int32 nanos = 2; 135 | } 136 | -------------------------------------------------------------------------------- /protos/google/protobuf/type.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/any.proto"; 36 | import "google/protobuf/source_context.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option cc_enable_arenas = true; 40 | option java_package = "com.google.protobuf"; 41 | option java_outer_classname = "TypeProto"; 42 | option java_multiple_files = true; 43 | option objc_class_prefix = "GPB"; 44 | option go_package = "google.golang.org/genproto/protobuf/ptype;ptype"; 45 | 46 | // A protocol buffer message type. 47 | message Type { 48 | // The fully qualified message name. 49 | string name = 1; 50 | // The list of fields. 51 | repeated Field fields = 2; 52 | // The list of types appearing in `oneof` definitions in this type. 53 | repeated string oneofs = 3; 54 | // The protocol buffer options. 55 | repeated Option options = 4; 56 | // The source context. 57 | SourceContext source_context = 5; 58 | // The source syntax. 59 | Syntax syntax = 6; 60 | } 61 | 62 | // A single field of a message type. 63 | message Field { 64 | // Basic field types. 65 | enum Kind { 66 | // Field type unknown. 67 | TYPE_UNKNOWN = 0; 68 | // Field type double. 69 | TYPE_DOUBLE = 1; 70 | // Field type float. 71 | TYPE_FLOAT = 2; 72 | // Field type int64. 73 | TYPE_INT64 = 3; 74 | // Field type uint64. 75 | TYPE_UINT64 = 4; 76 | // Field type int32. 77 | TYPE_INT32 = 5; 78 | // Field type fixed64. 79 | TYPE_FIXED64 = 6; 80 | // Field type fixed32. 81 | TYPE_FIXED32 = 7; 82 | // Field type bool. 83 | TYPE_BOOL = 8; 84 | // Field type string. 85 | TYPE_STRING = 9; 86 | // Field type group. Proto2 syntax only, and deprecated. 87 | TYPE_GROUP = 10; 88 | // Field type message. 89 | TYPE_MESSAGE = 11; 90 | // Field type bytes. 91 | TYPE_BYTES = 12; 92 | // Field type uint32. 93 | TYPE_UINT32 = 13; 94 | // Field type enum. 95 | TYPE_ENUM = 14; 96 | // Field type sfixed32. 97 | TYPE_SFIXED32 = 15; 98 | // Field type sfixed64. 99 | TYPE_SFIXED64 = 16; 100 | // Field type sint32. 101 | TYPE_SINT32 = 17; 102 | // Field type sint64. 103 | TYPE_SINT64 = 18; 104 | }; 105 | 106 | // Whether a field is optional, required, or repeated. 107 | enum Cardinality { 108 | // For fields with unknown cardinality. 109 | CARDINALITY_UNKNOWN = 0; 110 | // For optional fields. 111 | CARDINALITY_OPTIONAL = 1; 112 | // For required fields. Proto2 syntax only. 113 | CARDINALITY_REQUIRED = 2; 114 | // For repeated fields. 115 | CARDINALITY_REPEATED = 3; 116 | }; 117 | 118 | // The field type. 119 | Kind kind = 1; 120 | // The field cardinality. 121 | Cardinality cardinality = 2; 122 | // The field number. 123 | int32 number = 3; 124 | // The field name. 125 | string name = 4; 126 | // The field type URL, without the scheme, for message or enumeration 127 | // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. 128 | string type_url = 6; 129 | // The index of the field type in `Type.oneofs`, for message or enumeration 130 | // types. The first type has index 1; zero means the type is not in the list. 131 | int32 oneof_index = 7; 132 | // Whether to use alternative packed wire representation. 133 | bool packed = 8; 134 | // The protocol buffer options. 135 | repeated Option options = 9; 136 | // The field JSON name. 137 | string json_name = 10; 138 | // The string value of the default value of this field. Proto2 syntax only. 139 | string default_value = 11; 140 | } 141 | 142 | // Enum type definition. 143 | message Enum { 144 | // Enum type name. 145 | string name = 1; 146 | // Enum value definitions. 147 | repeated EnumValue enumvalue = 2; 148 | // Protocol buffer options. 149 | repeated Option options = 3; 150 | // The source context. 151 | SourceContext source_context = 4; 152 | // The source syntax. 153 | Syntax syntax = 5; 154 | } 155 | 156 | // Enum value definition. 157 | message EnumValue { 158 | // Enum value name. 159 | string name = 1; 160 | // Enum value number. 161 | int32 number = 2; 162 | // Protocol buffer options. 163 | repeated Option options = 3; 164 | } 165 | 166 | // A protocol buffer option, which can be attached to a message, field, 167 | // enumeration, etc. 168 | message Option { 169 | // The option's name. For protobuf built-in options (options defined in 170 | // descriptor.proto), this is the short name. For example, `"map_entry"`. 171 | // For custom options, it should be the fully-qualified name. For example, 172 | // `"google.api.http"`. 173 | string name = 1; 174 | // The option's value packed in an Any message. If the value is a primitive, 175 | // the corresponding wrapper type defined in google/protobuf/wrappers.proto 176 | // should be used. If the value is an enum, it should be stored as an int32 177 | // value using the google.protobuf.Int32Value type. 178 | Any value = 2; 179 | } 180 | 181 | // The syntax in which a protocol buffer element is defined. 182 | enum Syntax { 183 | // Syntax `proto2`. 184 | SYNTAX_PROTO2 = 0; 185 | // Syntax `proto3`. 186 | SYNTAX_PROTO3 = 1; 187 | } 188 | -------------------------------------------------------------------------------- /protos/google/protobuf/wrappers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Wrappers for primitive (non-message) types. These types are useful 32 | // for embedding primitives in the `google.protobuf.Any` type and for places 33 | // where we need to distinguish between the absence of a primitive 34 | // typed field and its default value. 35 | 36 | syntax = "proto3"; 37 | 38 | package google.protobuf; 39 | 40 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 41 | option cc_enable_arenas = true; 42 | option go_package = "github.com/golang/protobuf/ptypes/wrappers"; 43 | option java_package = "com.google.protobuf"; 44 | option java_outer_classname = "WrappersProto"; 45 | option java_multiple_files = true; 46 | option objc_class_prefix = "GPB"; 47 | 48 | // Wrapper message for `double`. 49 | // 50 | // The JSON representation for `DoubleValue` is JSON number. 51 | message DoubleValue { 52 | // The double value. 53 | double value = 1; 54 | } 55 | 56 | // Wrapper message for `float`. 57 | // 58 | // The JSON representation for `FloatValue` is JSON number. 59 | message FloatValue { 60 | // The float value. 61 | float value = 1; 62 | } 63 | 64 | // Wrapper message for `int64`. 65 | // 66 | // The JSON representation for `Int64Value` is JSON string. 67 | message Int64Value { 68 | // The int64 value. 69 | int64 value = 1; 70 | } 71 | 72 | // Wrapper message for `uint64`. 73 | // 74 | // The JSON representation for `UInt64Value` is JSON string. 75 | message UInt64Value { 76 | // The uint64 value. 77 | uint64 value = 1; 78 | } 79 | 80 | // Wrapper message for `int32`. 81 | // 82 | // The JSON representation for `Int32Value` is JSON number. 83 | message Int32Value { 84 | // The int32 value. 85 | int32 value = 1; 86 | } 87 | 88 | // Wrapper message for `uint32`. 89 | // 90 | // The JSON representation for `UInt32Value` is JSON number. 91 | message UInt32Value { 92 | // The uint32 value. 93 | uint32 value = 1; 94 | } 95 | 96 | // Wrapper message for `bool`. 97 | // 98 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 99 | message BoolValue { 100 | // The bool value. 101 | bool value = 1; 102 | } 103 | 104 | // Wrapper message for `string`. 105 | // 106 | // The JSON representation for `StringValue` is JSON string. 107 | message StringValue { 108 | // The string value. 109 | string value = 1; 110 | } 111 | 112 | // Wrapper message for `bytes`. 113 | // 114 | // The JSON representation for `BytesValue` is JSON string. 115 | message BytesValue { 116 | // The bytes value. 117 | bytes value = 1; 118 | } 119 | -------------------------------------------------------------------------------- /protos/netmessages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | //====== Copyright (c) 2013, Valve Corporation, All rights reserved. ========// 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // Redistributions of source code must retain the above copyright notice, this 9 | // list of conditions and the following disclaimer. 10 | // Redistributions in binary form must reproduce the above copyright notice, 11 | // this list of conditions and the following disclaimer in the documentation 12 | // and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 24 | // THE POSSIBILITY OF SUCH DAMAGE. 25 | //===========================================================================// 26 | // 27 | // Purpose: The file defines our Google Protocol Buffers which are used in over 28 | // the wire messages for the Source engine. 29 | // 30 | //============================================================================= 31 | 32 | // Note about encoding: 33 | // http://code.google.com/apis/protocolbuffers/docs/encoding.html 34 | // 35 | // TL;DR: Use sint32/sint64 for values that may be negative. 36 | // 37 | // There is an important difference between the signed int types (sint32 and sint64) 38 | // and the "standard" int types (int32 and int64) when it comes to encoding negative 39 | // numbers. If you use int32 or int64 as the type for a negative number, the 40 | // resulting varint is always ten bytes long � it is, effectively, treated like a 41 | // very large unsigned integer. If you use one of the signed types, the resulting 42 | // varint uses ZigZag encoding, which is much more efficient. 43 | 44 | 45 | // Commenting this out allows it to be compiled for SPEED or LITE_RUNTIME. 46 | // option optimize_for = SPEED; 47 | 48 | // We don't use the service generation functionality 49 | option cc_generic_services = false; 50 | 51 | 52 | // 53 | // STYLE NOTES: 54 | // 55 | // Use CamelCase CMsgMyMessageName style names for messages. 56 | // 57 | // Use lowercase _ delimited names like my_steam_id for field names, this is non-standard for Steam, 58 | // but plays nice with the Google formatted code generation. 59 | // 60 | // Try not to use required fields ever. Only do so if you are really really sure you'll never want them removed. 61 | // Optional should be preffered as it will make versioning easier and cleaner in the future if someone refactors 62 | // your message and wants to remove or rename fields. 63 | // 64 | // Use fixed64 for JobId_t, GID_t, or SteamID. This is appropriate for any field that is normally 65 | // going to be larger than 2^56. Otherwise use int64 for 64 bit values that are frequently smaller 66 | // than 2^56 as it will safe space on the wire in those cases. 67 | // 68 | // Similar to fixed64, use fixed32 for RTime32 or other 32 bit values that are frequently larger than 69 | // 2^28. It will safe space in those cases, otherwise use int32 which will safe space for smaller values. 70 | // An exception to this rule for RTime32 is if the value will frequently be zero rather than set to an actual 71 | // time. 72 | // 73 | 74 | import "google/protobuf/descriptor.proto"; 75 | 76 | //============================================================================= 77 | // Common Types 78 | //============================================================================= 79 | 80 | message CMsgVector 81 | { 82 | optional float x = 1; 83 | optional float y = 2; 84 | optional float z = 3; 85 | } 86 | 87 | message CMsgVector2D 88 | { 89 | optional float x = 1; 90 | optional float y = 2; 91 | } 92 | 93 | message CMsgQAngle 94 | { 95 | optional float x = 1; 96 | optional float y = 2; 97 | optional float z = 3; 98 | } 99 | 100 | message CMsgRGBA 101 | { 102 | optional int32 r = 1; 103 | optional int32 g = 2; 104 | optional int32 b = 3; 105 | optional int32 a = 4; 106 | } 107 | 108 | //============================================================================= 109 | // Bidirectional NET Messages 110 | //============================================================================= 111 | 112 | enum NET_Messages 113 | { 114 | net_NOP = 0; 115 | net_Disconnect = 1; // disconnect, last message in connection 116 | net_File = 2; // file transmission message request/deny 117 | net_SplitScreenUser = 3; // Changes split screen user, client and server must both provide handler 118 | net_Tick = 4; // s->c world tick, c->s ack world tick 119 | net_StringCmd = 5; // a string command 120 | net_SetConVar = 6; // sends one/multiple convar/userinfo settings 121 | net_SignonState = 7; // signals or acks current signon state 122 | net_PlayerAvatarData = 100; 123 | } 124 | 125 | message CNETMsg_Tick 126 | { 127 | optional uint32 tick = 1; // current tick count 128 | optional uint32 host_computationtime = 4; 129 | optional uint32 host_computationtime_std_deviation = 5; 130 | optional uint32 host_framestarttime_std_deviation = 6; 131 | optional uint32 hltv_replay_flags = 7; 132 | } 133 | 134 | message CNETMsg_StringCmd 135 | { 136 | optional string command = 1; 137 | } 138 | 139 | message CNETMsg_SignonState 140 | { 141 | optional uint32 signon_state = 1; // See SIGNONSTATE_ defines 142 | optional uint32 spawn_count = 2; // server spawn count (session number) 143 | optional uint32 num_server_players = 3; // Number of players the server discloses as connected to the server 144 | repeated string players_networkids = 4; // player network ids 145 | optional string map_name = 5; // Name of the current map 146 | } 147 | 148 | message CMsg_CVars 149 | { 150 | message CVar 151 | { 152 | optional string name = 1; 153 | optional string value = 2; 154 | optional uint32 dictionary_name = 3; 155 | } 156 | 157 | repeated CVar cvars = 1; 158 | } 159 | 160 | message CNETMsg_SetConVar 161 | { 162 | optional CMsg_CVars convars = 1; 163 | } 164 | 165 | message CNETMsg_NOP 166 | { 167 | } 168 | 169 | message CNETMsg_Disconnect 170 | { 171 | optional string text = 1; 172 | } 173 | 174 | message CNETMsg_File 175 | { 176 | optional int32 transfer_id = 1; 177 | optional string file_name = 2; 178 | optional bool is_replay_demo_file = 3; 179 | optional bool deny = 4; 180 | } 181 | 182 | message CNETMsg_SplitScreenUser 183 | { 184 | optional int32 slot = 1; 185 | } 186 | 187 | message CNETMsg_PlayerAvatarData { 188 | optional uint32 accountid = 1; 189 | optional bytes rgb = 2; 190 | } 191 | 192 | //============================================================================= 193 | // Client messages 194 | //============================================================================= 195 | 196 | enum CLC_Messages 197 | { 198 | clc_ClientInfo = 8; // client info (table CRC etc) 199 | clc_Move = 9; // [CUserCmd] 200 | clc_VoiceData = 10; // Voicestream data from a client 201 | clc_BaselineAck = 11; // client acknowledges a new baseline seqnr 202 | clc_ListenEvents = 12; // client acknowledges a new baseline seqnr 203 | clc_RespondCvarValue = 13; // client is responding to a svc_GetCvarValue message. 204 | clc_FileCRCCheck = 14; // client is sending a file's CRC to the server to be verified. 205 | clc_LoadingProgress = 15; // client loading progress 206 | clc_SplitPlayerConnect = 16; 207 | clc_ClientMessage = 17; 208 | clc_CmdKeyValues = 18; 209 | clc_HltvReplay = 20; 210 | } 211 | 212 | message CCLCMsg_ClientInfo 213 | { 214 | optional fixed32 send_table_crc = 1; 215 | optional uint32 server_count = 2; 216 | optional bool is_hltv = 3; 217 | optional bool is_replay = 4; 218 | optional uint32 friends_id = 5; 219 | optional string friends_name = 6; 220 | repeated fixed32 custom_files = 7; 221 | } 222 | 223 | message CCLCMsg_Move 224 | { 225 | optional uint32 num_backup_commands = 1; // new commands = user_cmds_size() - num_backup_commands 226 | optional uint32 num_new_commands = 2; 227 | optional bytes data = 3; 228 | } 229 | 230 | message CCLCMsg_VoiceData 231 | { 232 | optional bytes data = 1; 233 | optional fixed64 xuid = 2; 234 | optional VoiceDataFormat_t format = 3 [default = VOICEDATA_FORMAT_ENGINE]; 235 | optional int32 sequence_bytes = 4; 236 | optional uint32 section_number = 5; 237 | optional uint32 uncompressed_sample_offset = 6; 238 | } 239 | 240 | message CCLCMsg_BaselineAck 241 | { 242 | optional int32 baseline_tick = 1; 243 | optional int32 baseline_nr = 2; 244 | } 245 | 246 | message CCLCMsg_ListenEvents 247 | { 248 | repeated fixed32 event_mask = 1; 249 | } 250 | 251 | message CCLCMsg_RespondCvarValue 252 | { 253 | optional int32 cookie = 1; // QueryCvarCookie_t 254 | optional int32 status_code = 2; // EQueryCvarValueStatus 255 | optional string name = 3; 256 | optional string value = 4; 257 | } 258 | 259 | message CCLCMsg_FileCRCCheck 260 | { 261 | // See CCLCMsg_FileCRCCheck_t in netmessages.h while reading this. 262 | 263 | // Optimisation: 264 | 265 | // Do not set or get path or filename directly, use instead 266 | // CCLCMsg_FileCRCCheck_t::GetPath() 267 | // CCLCMsg_FileCRCCheck_t::GetPath()...etc.. 268 | 269 | // If the path and/or filename below is one of certain commonly occuring ones 270 | // then an index is stored instead of a string. So if code_path != -1 then 271 | // path == "". 272 | 273 | optional int32 code_path = 1; // read comment above 274 | optional string path = 2; // read comment above 275 | optional int32 code_filename = 3; // read comment above 276 | optional string filename = 4; // read comment above 277 | 278 | optional int32 file_fraction = 5; 279 | optional bytes md5 = 6; 280 | optional uint32 crc = 7; 281 | optional int32 file_hash_type = 8; 282 | optional int32 file_len = 9; 283 | optional int32 pack_file_id = 10; 284 | optional int32 pack_file_number = 11; 285 | } 286 | 287 | message CCLCMsg_LoadingProgress 288 | { 289 | optional int32 progress = 1; 290 | } 291 | 292 | message CCLCMsg_SplitPlayerConnect 293 | { 294 | optional CMsg_CVars convars = 1; 295 | } 296 | 297 | message CCLCMsg_CmdKeyValues 298 | { 299 | optional bytes keyvalues = 1; 300 | } 301 | 302 | 303 | //============================================================================= 304 | // Server messages 305 | //============================================================================= 306 | 307 | enum VoiceDataFormat_t { 308 | VOICEDATA_FORMAT_STEAM = 0; 309 | VOICEDATA_FORMAT_ENGINE = 1; 310 | } 311 | 312 | enum ESplitScreenMessageType 313 | { 314 | option allow_alias = true; 315 | MSG_SPLITSCREEN_ADDUSER = 0; 316 | MSG_SPLITSCREEN_REMOVEUSER = 1; 317 | MSG_SPLITSCREEN_TYPE_BITS = 1; 318 | }; 319 | 320 | enum ReplayEventType_t { 321 | REPLAY_EVENT_CANCEL = 0; 322 | REPLAY_EVENT_DEATH = 1; 323 | REPLAY_EVENT_GENERIC = 2; 324 | REPLAY_EVENT_STUCK_NEED_FULL_UPDATE = 3; 325 | } 326 | 327 | enum SVC_Messages 328 | { 329 | svc_ServerInfo = 8; // first message from server about game; map etc 330 | svc_SendTable = 9; // sends a sendtable description for a game class 331 | svc_ClassInfo = 10; // Info about classes (first byte is a CLASSINFO_ define). 332 | svc_SetPause = 11; // tells client if server paused or unpaused 333 | svc_CreateStringTable = 12; // inits shared string tables 334 | svc_UpdateStringTable = 13; // updates a string table 335 | svc_VoiceInit = 14; // inits used voice codecs & quality 336 | svc_VoiceData = 15; // Voicestream data from the server 337 | svc_Print = 16; // print text to console 338 | svc_Sounds = 17; // starts playing sound 339 | svc_SetView = 18; // sets entity as point of view 340 | svc_FixAngle = 19; // sets/corrects players viewangle 341 | svc_CrosshairAngle = 20; // adjusts crosshair in auto aim mode to lock on traget 342 | svc_BSPDecal = 21; // add a static decal to the world BSP 343 | svc_SplitScreen = 22; // split screen style message 344 | svc_UserMessage = 23; // a game specific message 345 | svc_EntityMessage = 24; // a message for an entity 346 | svc_GameEvent = 25; // global game event fired 347 | svc_PacketEntities = 26; // non-delta compressed entities 348 | svc_TempEntities = 27; // non-reliable event object 349 | svc_Prefetch = 28; // only sound indices for now 350 | svc_Menu = 29; // display a menu from a plugin 351 | svc_GameEventList = 30; // list of known games events and fields 352 | svc_GetCvarValue = 31; // Server wants to know the value of a cvar on the client 353 | svc_PaintmapData = 33; 354 | svc_CmdKeyValues = 34; // Server submits KeyValues command for the client 355 | svc_EncryptedData = 35; 356 | svc_HltvReplay = 36; 357 | svc_Broadcast_Command = 38; 358 | } 359 | 360 | message CSVCMsg_ServerInfo 361 | { 362 | optional int32 protocol = 1; // protocol version 363 | optional int32 server_count = 2; // number of changelevels since server start 364 | optional bool is_dedicated = 3; // dedicated server ? 365 | optional bool is_official_valve_server = 4; 366 | optional bool is_hltv = 5; // HLTV server ? 367 | optional bool is_replay = 6; // Replay server ? 368 | optional bool is_redirecting_to_proxy_relay = 21; // // Will be redirecting to proxy relay 369 | optional int32 c_os = 7; // L = linux, W = Win32 370 | optional fixed32 map_crc = 8; // server map CRC 371 | optional fixed32 client_crc = 9; // client.dll CRC server is using 372 | optional fixed32 string_table_crc = 10; // string table CRC server is using 373 | optional int32 max_clients = 11; // max number of clients on server 374 | optional int32 max_classes = 12; // max number of server classes 375 | optional int32 player_slot = 13; // our client slot number 376 | optional float tick_interval = 14; // server tick interval 377 | optional string game_dir = 15; // game directory eg "tf2" 378 | optional string map_name = 16; // name of current map 379 | optional string map_group_name = 17; // name of current map 380 | optional string sky_name = 18; // name of current skybox 381 | optional string host_name = 19; // server name 382 | optional uint32 public_ip = 20; 383 | optional uint64 ugc_map_id = 22; 384 | } 385 | 386 | message CSVCMsg_ClassInfo 387 | { 388 | message class_t 389 | { 390 | optional int32 class_id = 1; 391 | optional string data_table_name = 2; 392 | optional string class_name = 3; 393 | } 394 | 395 | optional bool create_on_client = 1; 396 | repeated class_t classes = 2; 397 | } 398 | 399 | message CSVCMsg_SendTable 400 | { 401 | message sendprop_t 402 | { 403 | optional int32 type = 1; // SendPropType 404 | optional string var_name = 2; 405 | optional int32 flags = 3; 406 | optional int32 priority = 4; 407 | optional string dt_name = 5; // if pProp->m_Type == DPT_DataTable || IsExcludeProp 408 | optional int32 num_elements = 6; // else if pProp->m_Type == DPT_Array 409 | optional float low_value = 7; // else ... 410 | optional float high_value = 8; // ... 411 | optional int32 num_bits = 9; // ... 412 | }; 413 | 414 | optional bool is_end = 1; 415 | optional string net_table_name = 2; 416 | optional bool needs_decoder = 3; 417 | repeated sendprop_t props = 4; 418 | } 419 | 420 | message CSVCMsg_Print 421 | { 422 | optional string text = 1; 423 | } 424 | 425 | message CSVCMsg_SetPause 426 | { 427 | optional bool paused = 1; 428 | } 429 | 430 | message CSVCMsg_SetView 431 | { 432 | optional int32 entity_index = 1; 433 | } 434 | 435 | message CSVCMsg_CreateStringTable 436 | { 437 | optional string name = 1; 438 | optional int32 max_entries = 2; 439 | optional int32 num_entries = 3; 440 | optional bool user_data_fixed_size = 4; 441 | optional int32 user_data_size = 5; 442 | optional int32 user_data_size_bits = 6; 443 | optional int32 flags = 7; 444 | optional bytes string_data = 8; 445 | } 446 | 447 | message CSVCMsg_UpdateStringTable 448 | { 449 | optional int32 table_id = 1; 450 | optional int32 num_changed_entries = 2; 451 | optional bytes string_data = 3; 452 | } 453 | 454 | message CSVCMsg_VoiceInit 455 | { 456 | optional int32 quality = 1; 457 | optional string codec = 2; 458 | optional int32 version = 3 [default = 0]; 459 | } 460 | 461 | message CSVCMsg_VoiceData 462 | { 463 | optional int32 client = 1; 464 | optional bool proximity = 2; 465 | optional fixed64 xuid = 3; 466 | optional int32 audible_mask = 4; 467 | optional bytes voice_data = 5; 468 | optional bool caster = 6; 469 | optional VoiceDataFormat_t format = 7 [default = VOICEDATA_FORMAT_ENGINE]; 470 | optional int32 sequence_bytes = 8; 471 | optional uint32 section_number = 9; 472 | optional uint32 uncompressed_sample_offset = 10; 473 | } 474 | 475 | message CSVCMsg_FixAngle 476 | { 477 | optional bool relative = 1; 478 | optional CMsgQAngle angle = 2; 479 | } 480 | 481 | message CSVCMsg_CrosshairAngle 482 | { 483 | optional CMsgQAngle angle = 1; 484 | } 485 | 486 | message CSVCMsg_Prefetch 487 | { 488 | optional int32 sound_index = 1; 489 | } 490 | 491 | message CSVCMsg_BSPDecal 492 | { 493 | optional CMsgVector pos = 1; 494 | optional int32 decal_texture_index = 2; 495 | optional int32 entity_index = 3; 496 | optional int32 model_index = 4; 497 | optional bool low_priority = 5; 498 | } 499 | 500 | message CSVCMsg_SplitScreen 501 | { 502 | optional ESplitScreenMessageType type = 1 [default = MSG_SPLITSCREEN_ADDUSER]; 503 | optional int32 slot = 2; 504 | optional int32 player_index = 3; 505 | } 506 | 507 | message CSVCMsg_GetCvarValue 508 | { 509 | optional int32 cookie = 1; // QueryCvarCookie_t 510 | optional string cvar_name = 2; 511 | } 512 | 513 | message CSVCMsg_Menu 514 | { 515 | optional int32 dialog_type = 1; // DIALOG_TYPE 516 | optional bytes menu_key_values = 2; // KeyValues.WriteAsBinary() 517 | } 518 | 519 | message CSVCMsg_UserMessage 520 | { 521 | optional int32 msg_type = 1; 522 | optional bytes msg_data = 2; 523 | optional int32 passthrough = 3; 524 | } 525 | 526 | message CSVCMsg_PaintmapData 527 | { 528 | optional bytes paintmap = 1; 529 | } 530 | 531 | message CSVCMsg_GameEvent 532 | { 533 | message key_t 534 | { 535 | optional int32 type = 1; // type 536 | optional string val_string = 2; // TYPE_STRING 537 | optional float val_float = 3; // TYPE_FLOAT 538 | optional int32 val_long = 4; // TYPE_LONG 539 | optional int32 val_short = 5; // TYPE_SHORT 540 | optional int32 val_byte = 6; // TYPE_BYTE 541 | optional bool val_bool = 7; // TYPE_BOOL 542 | optional uint64 val_uint64 = 8; // TYPE_UINT64 543 | optional bytes val_wstring = 9; // TYPE_WSTRING 544 | } 545 | 546 | optional string event_name = 1; 547 | optional int32 eventid = 2; 548 | repeated key_t keys = 3; 549 | optional int32 passthrough = 4; 550 | } 551 | 552 | message CSVCMsg_GameEventList 553 | { 554 | message key_t 555 | { 556 | optional int32 type = 1; 557 | optional string name = 2; 558 | }; 559 | 560 | message descriptor_t 561 | { 562 | optional int32 eventid = 1; 563 | optional string name = 2; 564 | repeated key_t keys = 3; 565 | }; 566 | 567 | repeated descriptor_t descriptors = 1; 568 | } 569 | 570 | message CSVCMsg_TempEntities 571 | { 572 | optional bool reliable = 1; 573 | optional int32 num_entries = 2; 574 | optional bytes entity_data = 3; 575 | } 576 | 577 | message CSVCMsg_PacketEntities 578 | { 579 | optional int32 max_entries = 1; 580 | optional int32 updated_entries = 2; 581 | optional bool is_delta = 3; 582 | optional bool update_baseline = 4; 583 | optional int32 baseline = 5; 584 | optional int32 delta_from = 6; 585 | optional bytes entity_data = 7; 586 | } 587 | 588 | message CSVCMsg_Sounds 589 | { 590 | message sounddata_t 591 | { 592 | optional sint32 origin_x = 1; 593 | optional sint32 origin_y = 2; 594 | optional sint32 origin_z = 3; 595 | optional uint32 volume = 4; 596 | optional float delay_value = 5; 597 | optional int32 sequence_number = 6; 598 | optional int32 entity_index = 7; 599 | optional int32 channel = 8; 600 | optional int32 pitch = 9; 601 | optional int32 flags = 10; 602 | optional uint32 sound_num = 11; 603 | optional fixed32 sound_num_handle = 12; 604 | optional int32 speaker_entity = 13; 605 | optional int32 random_seed = 14; 606 | optional int32 sound_level = 15; // soundlevel_t 607 | optional bool is_sentence = 16; 608 | optional bool is_ambient = 17; 609 | }; 610 | 611 | optional bool reliable_sound = 1; 612 | repeated sounddata_t sounds = 2; 613 | } 614 | 615 | message CSVCMsg_EntityMsg 616 | { 617 | optional int32 ent_index = 1; 618 | optional int32 class_id = 2; 619 | optional bytes ent_data = 3; 620 | } 621 | 622 | message CSVCMsg_CmdKeyValues 623 | { 624 | optional bytes keyvalues = 1; 625 | } 626 | 627 | message CSVCMsg_EncryptedData 628 | { 629 | optional bytes encrypted = 1; 630 | optional int32 key_type = 2; 631 | } 632 | 633 | message CSVCMsg_HltvReplay { 634 | optional int32 delay = 1; 635 | optional int32 primary_target = 2; 636 | optional int32 replay_stop_at = 3; 637 | optional int32 replay_start_at = 4; 638 | optional int32 replay_slowdown_begin = 5; 639 | optional int32 replay_slowdown_end = 6; 640 | optional float replay_slowdown_rate = 7; 641 | } 642 | 643 | message CCLCMsg_HltvReplay { 644 | optional int32 request = 1; 645 | optional float slowdown_length = 2; 646 | optional float slowdown_rate = 3; 647 | optional int32 primary_target_ent_index = 4; 648 | optional float event_time = 5; 649 | } 650 | 651 | message CSVCMsg_Broadcast_Command { 652 | optional string cmd = 1; 653 | } 654 | -------------------------------------------------------------------------------- /protos/steammessages.proto: -------------------------------------------------------------------------------- 1 | import "google/protobuf/descriptor.proto"; 2 | 3 | option optimize_for = SPEED; 4 | option cc_generic_services = false; 5 | 6 | extend .google.protobuf.FieldOptions { 7 | optional bool key_field = 60000 [default = false]; 8 | } 9 | 10 | extend .google.protobuf.MessageOptions { 11 | optional int32 msgpool_soft_limit = 60000 [default = 32]; 12 | optional int32 msgpool_hard_limit = 60001 [default = 384]; 13 | } 14 | 15 | enum GCProtoBufMsgSrc { 16 | GCProtoBufMsgSrc_Unspecified = 0; 17 | GCProtoBufMsgSrc_FromSystem = 1; 18 | GCProtoBufMsgSrc_FromSteamID = 2; 19 | GCProtoBufMsgSrc_FromGC = 3; 20 | GCProtoBufMsgSrc_ReplySystem = 4; 21 | } 22 | 23 | message CMsgProtoBufHeader { 24 | option (msgpool_soft_limit) = 256; 25 | option (msgpool_hard_limit) = 1024; 26 | 27 | optional fixed64 client_steam_id = 1; 28 | optional int32 client_session_id = 2; 29 | optional uint32 source_app_id = 3; 30 | optional fixed64 job_id_source = 10 [default = 18446744073709551615]; 31 | optional fixed64 job_id_target = 11 [default = 18446744073709551615]; 32 | optional string target_job_name = 12; 33 | optional int32 eresult = 13 [default = 2]; 34 | optional string error_message = 14; 35 | optional uint32 ip = 15; 36 | optional .GCProtoBufMsgSrc gc_msg_src = 200 [default = GCProtoBufMsgSrc_Unspecified]; 37 | optional uint32 gc_dir_index_source = 201; 38 | } 39 | 40 | message CMsgWebAPIKey { 41 | optional uint32 status = 1 [default = 255]; 42 | optional uint32 account_id = 2 [default = 0]; 43 | optional uint32 publisher_group_id = 3 [default = 0]; 44 | optional uint32 key_id = 4; 45 | optional string domain = 5; 46 | } 47 | 48 | message CMsgHttpRequest { 49 | message RequestHeader { 50 | optional string name = 1; 51 | optional string value = 2; 52 | } 53 | 54 | message QueryParam { 55 | optional string name = 1; 56 | optional bytes value = 2; 57 | } 58 | 59 | optional uint32 request_method = 1; 60 | optional string hostname = 2; 61 | optional string url = 3; 62 | repeated .CMsgHttpRequest.RequestHeader headers = 4; 63 | repeated .CMsgHttpRequest.QueryParam get_params = 5; 64 | repeated .CMsgHttpRequest.QueryParam post_params = 6; 65 | optional bytes body = 7; 66 | optional uint32 absolute_timeout = 8; 67 | } 68 | 69 | message CMsgWebAPIRequest { 70 | optional string UNUSED_job_name = 1; 71 | optional string interface_name = 2; 72 | optional string method_name = 3; 73 | optional uint32 version = 4; 74 | optional .CMsgWebAPIKey api_key = 5; 75 | optional .CMsgHttpRequest request = 6; 76 | optional uint32 routing_app_id = 7; 77 | } 78 | 79 | message CMsgHttpResponse { 80 | message ResponseHeader { 81 | optional string name = 1; 82 | optional string value = 2; 83 | } 84 | 85 | optional uint32 status_code = 1; 86 | repeated .CMsgHttpResponse.ResponseHeader headers = 2; 87 | optional bytes body = 3; 88 | } 89 | 90 | message CMsgAMFindAccounts { 91 | optional uint32 search_type = 1; 92 | optional string search_string = 2; 93 | } 94 | 95 | message CMsgAMFindAccountsResponse { 96 | repeated fixed64 steam_id = 1; 97 | } 98 | 99 | message CMsgNotifyWatchdog { 100 | optional uint32 source = 1; 101 | optional uint32 alert_type = 2; 102 | optional uint32 alert_destination = 3; 103 | optional bool critical = 4; 104 | optional uint32 time = 5; 105 | optional uint32 appid = 6; 106 | optional string text = 7; 107 | } 108 | 109 | message CMsgAMGetLicenses { 110 | optional fixed64 steamid = 1; 111 | } 112 | 113 | message CMsgPackageLicense { 114 | optional uint32 package_id = 1; 115 | optional uint32 time_created = 2; 116 | optional uint32 owner_id = 3; 117 | } 118 | 119 | message CMsgAMGetLicensesResponse { 120 | repeated .CMsgPackageLicense license = 1; 121 | optional uint32 result = 2; 122 | } 123 | 124 | message CMsgAMGetUserGameStats { 125 | optional fixed64 steam_id = 1; 126 | optional fixed64 game_id = 2; 127 | repeated uint32 stats = 3; 128 | } 129 | 130 | message CMsgAMGetUserGameStatsResponse { 131 | message Stats { 132 | optional uint32 stat_id = 1; 133 | optional uint32 stat_value = 2; 134 | } 135 | 136 | message Achievement_Blocks { 137 | optional uint32 achievement_id = 1; 138 | optional uint32 achievement_bit_id = 2; 139 | optional fixed32 unlock_time = 3; 140 | } 141 | 142 | optional fixed64 steam_id = 1; 143 | optional fixed64 game_id = 2; 144 | optional int32 eresult = 3 [default = 2]; 145 | repeated .CMsgAMGetUserGameStatsResponse.Stats stats = 4; 146 | repeated .CMsgAMGetUserGameStatsResponse.Achievement_Blocks achievement_blocks = 5; 147 | } 148 | 149 | message CMsgGCGetCommandList { 150 | optional uint32 app_id = 1; 151 | optional string command_prefix = 2; 152 | } 153 | 154 | message CMsgGCGetCommandListResponse { 155 | repeated string command_name = 1; 156 | } 157 | 158 | message CGCMsgMemCachedGet { 159 | repeated string keys = 1; 160 | } 161 | 162 | message CGCMsgMemCachedGetResponse { 163 | message ValueTag { 164 | optional bool found = 1; 165 | optional bytes value = 2; 166 | } 167 | 168 | repeated .CGCMsgMemCachedGetResponse.ValueTag values = 1; 169 | } 170 | 171 | message CGCMsgMemCachedSet { 172 | message KeyPair { 173 | optional string name = 1; 174 | optional bytes value = 2; 175 | } 176 | 177 | repeated .CGCMsgMemCachedSet.KeyPair keys = 1; 178 | } 179 | 180 | message CGCMsgMemCachedDelete { 181 | repeated string keys = 1; 182 | } 183 | 184 | message CGCMsgMemCachedStats { 185 | } 186 | 187 | message CGCMsgMemCachedStatsResponse { 188 | optional uint64 curr_connections = 1; 189 | optional uint64 cmd_get = 2; 190 | optional uint64 cmd_set = 3; 191 | optional uint64 cmd_flush = 4; 192 | optional uint64 get_hits = 5; 193 | optional uint64 get_misses = 6; 194 | optional uint64 delete_hits = 7; 195 | optional uint64 delete_misses = 8; 196 | optional uint64 bytes_read = 9; 197 | optional uint64 bytes_written = 10; 198 | optional uint64 limit_maxbytes = 11; 199 | optional uint64 curr_items = 12; 200 | optional uint64 evictions = 13; 201 | optional uint64 bytes = 14; 202 | } 203 | 204 | message CGCMsgSQLStats { 205 | optional uint32 schema_catalog = 1; 206 | } 207 | 208 | message CGCMsgSQLStatsResponse { 209 | optional uint32 threads = 1; 210 | optional uint32 threads_connected = 2; 211 | optional uint32 threads_active = 3; 212 | optional uint32 operations_submitted = 4; 213 | optional uint32 prepared_statements_executed = 5; 214 | optional uint32 non_prepared_statements_executed = 6; 215 | optional uint32 deadlock_retries = 7; 216 | optional uint32 operations_timed_out_in_queue = 8; 217 | optional uint32 errors = 9; 218 | } 219 | 220 | message CMsgAMAddFreeLicense { 221 | optional fixed64 steamid = 1; 222 | optional uint32 ip_public = 2; 223 | optional uint32 packageid = 3; 224 | optional string store_country_code = 4; 225 | } 226 | 227 | message CMsgAMAddFreeLicenseResponse { 228 | optional int32 eresult = 1 [default = 2]; 229 | optional int32 purchase_result_detail = 2; 230 | optional fixed64 transid = 3; 231 | } 232 | 233 | message CGCMsgGetIPLocation { 234 | repeated fixed32 ips = 1; 235 | } 236 | 237 | message CIPLocationInfo { 238 | optional uint32 ip = 1; 239 | optional float latitude = 2; 240 | optional float longitude = 3; 241 | optional string country = 4; 242 | optional string state = 5; 243 | optional string city = 6; 244 | } 245 | 246 | message CGCMsgGetIPLocationResponse { 247 | repeated .CIPLocationInfo infos = 1; 248 | } 249 | 250 | message CGCMsgSystemStatsSchema { 251 | optional uint32 gc_app_id = 1; 252 | optional bytes schema_kv = 2; 253 | } 254 | 255 | message CGCMsgGetSystemStats { 256 | } 257 | 258 | message CGCMsgGetSystemStatsResponse { 259 | optional uint32 gc_app_id = 1; 260 | optional bytes stats_kv = 2; 261 | optional uint32 active_jobs = 3; 262 | optional uint32 yielding_jobs = 4; 263 | optional uint32 user_sessions = 5; 264 | optional uint32 game_server_sessions = 6; 265 | optional uint32 socaches = 7; 266 | optional uint32 socaches_to_unload = 8; 267 | optional uint32 socaches_loading = 9; 268 | optional uint32 writeback_queue = 10; 269 | optional uint32 steamid_locks = 11; 270 | optional uint32 logon_queue = 12; 271 | optional uint32 logon_jobs = 13; 272 | } 273 | 274 | message CMsgAMSendEmail { 275 | message ReplacementToken { 276 | optional string token_name = 1; 277 | optional string token_value = 2; 278 | } 279 | 280 | message PersonaNameReplacementToken { 281 | optional fixed64 steamid = 1; 282 | optional string token_name = 2; 283 | } 284 | 285 | optional fixed64 steamid = 1; 286 | optional uint32 email_msg_type = 2; 287 | optional uint32 email_format = 3; 288 | repeated .CMsgAMSendEmail.PersonaNameReplacementToken persona_name_tokens = 5; 289 | optional uint32 source_gc = 6; 290 | repeated .CMsgAMSendEmail.ReplacementToken tokens = 7; 291 | } 292 | 293 | message CMsgAMSendEmailResponse { 294 | optional uint32 eresult = 1 [default = 2]; 295 | } 296 | 297 | message CMsgGCGetEmailTemplate { 298 | optional uint32 app_id = 1; 299 | optional uint32 email_msg_type = 2; 300 | optional int32 email_lang = 3; 301 | optional int32 email_format = 4; 302 | } 303 | 304 | message CMsgGCGetEmailTemplateResponse { 305 | optional uint32 eresult = 1 [default = 2]; 306 | optional bool template_exists = 2; 307 | optional string template = 3; 308 | } 309 | 310 | message CMsgAMGrantGuestPasses2 { 311 | optional fixed64 steam_id = 1; 312 | optional uint32 package_id = 2; 313 | optional int32 passes_to_grant = 3; 314 | optional int32 days_to_expiration = 4; 315 | optional int32 action = 5; 316 | } 317 | 318 | message CMsgAMGrantGuestPasses2Response { 319 | optional int32 eresult = 1 [default = 2]; 320 | optional int32 passes_granted = 2 [default = 0]; 321 | } 322 | 323 | message CGCSystemMsg_GetAccountDetails { 324 | option (msgpool_soft_limit) = 128; 325 | option (msgpool_hard_limit) = 512; 326 | 327 | optional fixed64 steamid = 1; 328 | optional uint32 appid = 2; 329 | } 330 | 331 | message CGCSystemMsg_GetAccountDetails_Response { 332 | option (msgpool_soft_limit) = 128; 333 | option (msgpool_hard_limit) = 512; 334 | 335 | optional uint32 eresult_deprecated = 1 [default = 2]; 336 | optional string account_name = 2; 337 | optional string persona_name = 3; 338 | optional bool is_profile_public = 4; 339 | optional bool is_inventory_public = 5; 340 | optional bool is_vac_banned = 7; 341 | optional bool is_cyber_cafe = 8; 342 | optional bool is_school_account = 9; 343 | optional bool is_limited = 10; 344 | optional bool is_subscribed = 11; 345 | optional uint32 package = 12; 346 | optional bool is_free_trial_account = 13; 347 | optional uint32 free_trial_expiration = 14; 348 | optional bool is_low_violence = 15; 349 | optional bool is_account_locked_down = 16; 350 | optional bool is_community_banned = 17; 351 | optional bool is_trade_banned = 18; 352 | optional uint32 trade_ban_expiration = 19; 353 | optional uint32 accountid = 20; 354 | optional uint32 suspension_end_time = 21; 355 | optional string currency = 22; 356 | optional uint32 steam_level = 23; 357 | optional uint32 friend_count = 24; 358 | optional uint32 account_creation_time = 25; 359 | optional bool is_steamguard_enabled = 27; 360 | optional bool is_phone_verified = 28; 361 | optional bool is_two_factor_auth_enabled = 29; 362 | optional uint32 two_factor_enabled_time = 30; 363 | optional uint32 phone_verification_time = 31; 364 | optional uint64 phone_id = 33; 365 | optional bool is_phone_identifying = 34; 366 | optional uint32 rt_identity_linked = 35; 367 | optional uint32 rt_birth_date = 36; 368 | optional string txn_country_code = 37; 369 | } 370 | 371 | message CMsgGCGetPersonaNames { 372 | repeated fixed64 steamids = 1; 373 | } 374 | 375 | message CMsgGCGetPersonaNames_Response { 376 | message PersonaName { 377 | optional fixed64 steamid = 1; 378 | optional string persona_name = 2; 379 | } 380 | 381 | repeated .CMsgGCGetPersonaNames_Response.PersonaName succeeded_lookups = 1; 382 | repeated fixed64 failed_lookup_steamids = 2; 383 | } 384 | 385 | message CMsgGCCheckFriendship { 386 | optional fixed64 steamid_left = 1; 387 | optional fixed64 steamid_right = 2; 388 | } 389 | 390 | message CMsgGCCheckFriendship_Response { 391 | optional bool success = 1; 392 | optional bool found_friendship = 2; 393 | } 394 | 395 | message CMsgGCMsgMasterSetDirectory { 396 | message SubGC { 397 | optional uint32 dir_index = 1; 398 | optional string name = 2; 399 | optional string box = 3; 400 | optional string command_line = 4; 401 | optional string gc_binary = 5; 402 | } 403 | 404 | optional uint32 master_dir_index = 1; 405 | repeated .CMsgGCMsgMasterSetDirectory.SubGC dir = 2; 406 | } 407 | 408 | message CMsgGCMsgMasterSetDirectory_Response { 409 | optional int32 eresult = 1 [default = 2]; 410 | } 411 | 412 | message CMsgGCMsgWebAPIJobRequestForwardResponse { 413 | optional uint32 dir_index = 1; 414 | } 415 | 416 | message CGCSystemMsg_GetPurchaseTrust_Request { 417 | optional fixed64 steamid = 1; 418 | } 419 | 420 | message CGCSystemMsg_GetPurchaseTrust_Response { 421 | optional bool has_prior_purchase_history = 1; 422 | optional bool has_no_recent_password_resets = 2; 423 | optional bool is_wallet_cash_trusted = 3; 424 | optional uint32 time_all_trusted = 4; 425 | } 426 | 427 | message CMsgGCHAccountVacStatusChange { 428 | optional fixed64 steam_id = 1; 429 | optional uint32 app_id = 2; 430 | optional uint32 rtime_vacban_starts = 3; 431 | optional bool is_banned_now = 4; 432 | optional bool is_banned_future = 5; 433 | } 434 | 435 | message CMsgGCGetPartnerAccountLink { 436 | optional fixed64 steamid = 1; 437 | } 438 | 439 | message CMsgGCGetPartnerAccountLink_Response { 440 | optional uint32 pwid = 1; 441 | optional uint32 nexonid = 2; 442 | optional int32 ageclass = 3; 443 | optional bool id_verified = 4 [default = true]; 444 | optional bool is_adult = 5; 445 | } 446 | 447 | message CMsgGCRoutingInfo { 448 | enum RoutingMethod { 449 | RANDOM = 0; 450 | DISCARD = 1; 451 | CLIENT_STEAMID = 2; 452 | PROTOBUF_FIELD_UINT64 = 3; 453 | WEBAPI_PARAM_UINT64 = 4; 454 | } 455 | 456 | repeated uint32 dir_index = 1; 457 | optional .CMsgGCRoutingInfo.RoutingMethod method = 2 [default = RANDOM]; 458 | optional .CMsgGCRoutingInfo.RoutingMethod fallback = 3 [default = DISCARD]; 459 | optional uint32 protobuf_field = 4; 460 | optional string webapi_param = 5; 461 | } 462 | 463 | message CMsgGCMsgMasterSetWebAPIRouting { 464 | message Entry { 465 | optional string interface_name = 1; 466 | optional string method_name = 2; 467 | optional .CMsgGCRoutingInfo routing = 3; 468 | } 469 | 470 | repeated .CMsgGCMsgMasterSetWebAPIRouting.Entry entries = 1; 471 | } 472 | 473 | message CMsgGCMsgMasterSetClientMsgRouting { 474 | message Entry { 475 | optional uint32 msg_type = 1; 476 | optional .CMsgGCRoutingInfo routing = 2; 477 | } 478 | 479 | repeated .CMsgGCMsgMasterSetClientMsgRouting.Entry entries = 1; 480 | } 481 | 482 | message CMsgGCMsgMasterSetWebAPIRouting_Response { 483 | optional int32 eresult = 1 [default = 2]; 484 | } 485 | 486 | message CMsgGCMsgMasterSetClientMsgRouting_Response { 487 | optional int32 eresult = 1 [default = 2]; 488 | } 489 | 490 | message CMsgGCMsgSetOptions { 491 | message MessageRange { 492 | required uint32 low = 1; 493 | required uint32 high = 2; 494 | } 495 | 496 | enum Option { 497 | NOTIFY_USER_SESSIONS = 0; 498 | NOTIFY_SERVER_SESSIONS = 1; 499 | NOTIFY_ACHIEVEMENTS = 2; 500 | NOTIFY_VAC_ACTION = 3; 501 | } 502 | 503 | repeated .CMsgGCMsgSetOptions.Option options = 1; 504 | repeated .CMsgGCMsgSetOptions.MessageRange client_msg_ranges = 2; 505 | } 506 | 507 | message CMsgGCHUpdateSession { 508 | message ExtraField { 509 | optional string name = 1; 510 | optional string value = 2; 511 | } 512 | 513 | optional fixed64 steam_id = 1; 514 | optional uint32 app_id = 2; 515 | optional bool online = 3; 516 | optional fixed64 server_steam_id = 4; 517 | optional uint32 server_addr = 5; 518 | optional uint32 server_port = 6; 519 | optional uint32 os_type = 7; 520 | optional uint32 client_addr = 8; 521 | repeated .CMsgGCHUpdateSession.ExtraField extra_fields = 9; 522 | optional fixed64 owner_id = 10; 523 | optional uint32 cm_session_sysid = 11; 524 | optional uint32 cm_session_identifier = 12; 525 | repeated uint32 depot_ids = 13; 526 | } 527 | 528 | message CMsgNotificationOfSuspiciousActivity { 529 | message MultipleGameInstances { 530 | optional uint32 app_instance_count = 1; 531 | repeated fixed64 other_steamids = 2; 532 | } 533 | 534 | optional fixed64 steamid = 1; 535 | optional uint32 appid = 2; 536 | optional .CMsgNotificationOfSuspiciousActivity.MultipleGameInstances multiple_instances = 3; 537 | } 538 | 539 | message CMsgDPPartnerMicroTxns { 540 | message PartnerMicroTxn { 541 | optional uint32 init_time = 1; 542 | optional uint32 last_update_time = 2; 543 | optional uint64 txn_id = 3; 544 | optional uint32 account_id = 4; 545 | optional uint32 line_item = 5; 546 | optional uint64 item_id = 6; 547 | optional uint32 def_index = 7; 548 | optional uint64 price = 8; 549 | optional uint64 tax = 9; 550 | optional uint64 price_usd = 10; 551 | optional uint64 tax_usd = 11; 552 | optional uint32 purchase_type = 12; 553 | optional uint32 steam_txn_type = 13; 554 | optional string country_code = 14; 555 | optional string region_code = 15; 556 | optional int32 quantity = 16; 557 | optional uint64 ref_trans_id = 17; 558 | } 559 | 560 | message PartnerInfo { 561 | optional uint32 partner_id = 1; 562 | optional string partner_name = 2; 563 | optional string currency_code = 3; 564 | optional string currency_name = 4; 565 | } 566 | 567 | optional uint32 appid = 1; 568 | optional string gc_name = 2; 569 | optional .CMsgDPPartnerMicroTxns.PartnerInfo partner = 3; 570 | repeated .CMsgDPPartnerMicroTxns.PartnerMicroTxn transactions = 4; 571 | } 572 | 573 | message CMsgDPPartnerMicroTxnsResponse { 574 | enum EErrorCode { 575 | k_MsgValid = 0; 576 | k_MsgInvalidAppID = 1; 577 | k_MsgInvalidPartnerInfo = 2; 578 | k_MsgNoTransactions = 3; 579 | k_MsgSQLFailure = 4; 580 | k_MsgPartnerInfoDiscrepancy = 5; 581 | k_MsgTransactionInsertFailed = 7; 582 | k_MsgAlreadyRunning = 8; 583 | k_MsgInvalidTransactionData = 9; 584 | } 585 | 586 | optional uint32 eresult = 1 [default = 2]; 587 | optional .CMsgDPPartnerMicroTxnsResponse.EErrorCode eerrorcode = 2 [default = k_MsgValid]; 588 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate enum_dispatch; 3 | 4 | #[macro_use] 5 | extern crate num_derive; 6 | 7 | mod source; 8 | mod steam; 9 | mod protoutil; 10 | use source::ConnectionlessChannel; 11 | use source::packets::*; 12 | use steam::SteamClient; 13 | use source::protos::{CMsg_CVars, CCLCMsg_SplitPlayerConnect, CMsg_CVars_CVar}; 14 | use source::NetChannel; 15 | 16 | use std::net::{UdpSocket, IpAddr}; 17 | use crate::source::netmessages::NetMessage; 18 | use crate::source::protos::NET_Messages; 19 | use log::{info, debug, trace}; 20 | 21 | fn run() -> anyhow::Result<()> 22 | { 23 | pretty_env_logger::init(); 24 | 25 | info!("Connecting to Steam..."); 26 | let _steam = SteamClient::connect()?; 27 | //_steam.request_join_server(13759, ) 28 | info!("Connected to Steam!"); 29 | 30 | // bind to some client socket 31 | let socket = UdpSocket::bind("0.0.0.0:0")?; 32 | 33 | // "connect" to udp server 34 | socket.connect("192.168.201.128:6543")?; 35 | let addr = socket.peer_addr()?; 36 | 37 | // promote to a connectionless netchannel 38 | let mut stream = ConnectionlessChannel::new(socket)?; 39 | 40 | // request server info 41 | let packet = A2sInfo::default(); 42 | //dbg!(&packet); 43 | stream.send_packet(packet.into())?; 44 | 45 | // receive server info response 46 | let _res: S2aInfoSrc = stream.recv_packet_type()?; 47 | //dbg!(&_res); 48 | 49 | // request challenge 50 | let packet = A2sGetChallenge::default(); 51 | //dbg!(&packet); 52 | stream.send_packet(packet.into())?; 53 | 54 | // receive challenge response 55 | let _res: S2cChallenge = stream.recv_packet_type()?; 56 | //dbg!(&_res); 57 | 58 | // verify the challenge 59 | let packet = A2sGetChallenge::with_challenge(_res.challenge_num); 60 | //dbg!(&packet); 61 | stream.send_packet(packet.into())?; 62 | 63 | // ensure we have successfully verified the challenge 64 | let chal: S2cChallenge = stream.recv_packet_type()?; 65 | //dbg!(&_res); 66 | 67 | let ip_encoded: u32; 68 | if let IpAddr::V4(ip) = addr.ip() 69 | { 70 | ip_encoded = u32::from(ip); 71 | } 72 | else { 73 | panic!("ipv6 not supported by source engine"); 74 | } 75 | 76 | // request to join the server through the game coordinator 77 | // this makes the game coordinator contact the server and tell it that we're about 78 | // to connect, which generates a reservationid that we must pass in the C2S_CONNECT 79 | // packet in order to prove that we have registered our connection to the game coordinator 80 | let reservation = _steam.request_join_server( 81 | chal.host_version, 82 | chal.gameserver_steamid, 83 | ip_encoded, 84 | addr.port() as u32 85 | )?; 86 | 87 | // now we need to ask the steamworks api to generate our client an authentication ticket 88 | // to send to the server 89 | // 90 | // this ticket is basically an encrypted blob which is signed by the steam backend which proves 91 | // that we own the game we are trying to use and that we are who we say we are (so the server 92 | // can properly assign our steamid) 93 | let auth_ticket = _steam.get_auth_ticket()?; 94 | info!("Ticket length: {}", auth_ticket.len()); 95 | info!("SteamID: {}", _steam.get_steam_id().raw()); 96 | info!("ReservationID: {}", reservation.reservationid); 97 | 98 | 99 | let auth = SteamAuthInfo { 100 | steamid: _steam.get_steam_id().raw(), 101 | auth_ticket, 102 | }; 103 | 104 | // this protobuf packet is encoded directly into the C2S_CONNECT packet 105 | // it contains all of our userinfo convars, and some of them are verified for integrity 106 | // in the authentication process 107 | let mut split_connect = CCLCMsg_SplitPlayerConnect::new(); 108 | let mut convars = CMsg_CVars::new(); 109 | 110 | let mut cvar = CMsg_CVars_CVar::new(); 111 | cvar.set_name("cl_session".to_string()); 112 | cvar.set_value(format!("${:#x}", reservation.reservationid)); 113 | 114 | convars.cvars.push(cvar); 115 | 116 | split_connect.set_convars(convars); 117 | 118 | let mut player_connects = Vec::with_capacity(1); 119 | player_connects.push(split_connect); 120 | 121 | let conn = C2sConnect{ 122 | host_version: chal.host_version, 123 | auth_protocol: AuthProtocolType::PROTOCOL_STEAM, 124 | challenge_num: chal.challenge_num, 125 | player_name: String::new(), // not used cs:go, uses "name" from the protobuf above^ 126 | server_password: String::from("a59CdkwjR4"), 127 | num_players: 1, // no split screen 128 | split_player_connect: player_connects, 129 | low_violence: false, 130 | lobby_cookie: reservation.reservationid, 131 | crossplay_platform: CrossplayPlatform::Pc, 132 | encryption_key_index: 0, // no steam2 cert encryption 133 | auth_info: auth, 134 | }; 135 | 136 | // send off the connect packet 137 | stream.send_packet(conn.into())?; 138 | 139 | // assuming everything worked out, we should get S2CConnection back, which means we have established 140 | // a netchannel 141 | // we actually receive two different S2C_Connection packets, neither of them actually matter. 142 | let _connection_pkt: S2cConnection = stream.recv_packet_type()?; 143 | let _connection_pkt: S2cConnection = stream.recv_packet_type()?; 144 | debug!("Connect packet: {:?}", &_connection_pkt); 145 | info!("Successfully established a netchannel."); 146 | 147 | let mut channel = NetChannel::upgrade(stream, chal.host_version)?; 148 | let mut signon = source::protos::CNETMsg_SignonState::new(); 149 | signon.set_signon_state(2); 150 | 151 | let msg = NetMessage::from_proto(Box::new(signon), NET_Messages::net_SignonState as i32); 152 | let err = channel.write_netmessage(msg); 153 | debug!("Packet result: {:?}", &err); 154 | 155 | loop{ 156 | // read incoming data 157 | let datagram = channel.read_data()?; 158 | if let Some(messages) = datagram.get_messages() 159 | { 160 | for msg in messages.into_iter() 161 | { 162 | trace!("* {}", msg.get_type_name()); 163 | } 164 | } 165 | 166 | // blank message just to keep the other side updated 167 | channel.write_nop()?; 168 | } 169 | //::std::thread::sleep(std::time::Duration::from_millis(10000)); 170 | //Ok(()) 171 | } 172 | 173 | fn main() { 174 | 175 | let res: anyhow::Result<()> = run(); 176 | 177 | if let Err(e) = res 178 | { 179 | info!("ERROR: {:?}", e); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/protoutil/mod.rs: -------------------------------------------------------------------------------- 1 | mod protoutil; 2 | pub use protoutil::*; -------------------------------------------------------------------------------- /src/protoutil/protoutil.rs: -------------------------------------------------------------------------------- 1 | // Deserialize a protobuf message from its bytes representation 2 | pub fn deserialize(bytes: &[u8]) -> anyhow::Result 3 | where M: protobuf::Message 4 | { 5 | let res = protobuf::parse_from_bytes::(&bytes)?; 6 | return Ok(res); 7 | } 8 | 9 | /// Serialize a protobuf message into its bytes representation 10 | pub fn serialize(proto_msg: M) -> anyhow::Result> 11 | where M: protobuf::Message 12 | { 13 | // establish some vector space 14 | let mut vec = Vec::with_capacity(proto_msg.get_cached_size() as usize); 15 | 16 | // write the message to the vector 17 | proto_msg.write_to_vec(&mut vec)?; 18 | 19 | return Ok(vec); 20 | } 21 | 22 | /// Clears the buffer and writes a protobuf message to it 23 | pub fn serialize_to_buffer(proto_msg: &M, buf: &mut Vec) -> anyhow::Result<()> 24 | where M: protobuf::Message 25 | { 26 | // clear space for message 27 | buf.clear(); 28 | 29 | // compute size and resize buffer capacity to fit 30 | let len = proto_msg.compute_size() as usize; 31 | if buf.capacity() < len { 32 | buf.reserve(len - buf.len()); 33 | } 34 | 35 | // write the message to the buf 36 | proto_msg.write_to_vec(buf)?; 37 | 38 | return Ok(()) 39 | } -------------------------------------------------------------------------------- /src/source/bitbuf.rs: -------------------------------------------------------------------------------- 1 | pub use bitstream_io::{LittleEndian, BitReader, BitWriter}; 2 | use anyhow::{Result}; 3 | 4 | // A bit buffer reader which reads bits in little endian 5 | // Used for reading messages from a stream since some netmessages are bit-based 6 | pub type BitBufReaderType<'a> = BitReader, LittleEndian>; 7 | 8 | // A bit buffer writer type which writes bits in little endian 9 | // Used for writing messages to a stream 10 | pub type BitBufWriterType<'a> = BitWriter>, LittleEndian>; 11 | 12 | // read useful types from a bit buffer 13 | pub trait WireReader 14 | { 15 | fn read_long(&mut self) -> Result; 16 | fn read_longlong(&mut self) -> Result; 17 | fn read_word(&mut self) -> Result; 18 | fn read_char(&mut self) -> Result; 19 | fn read_string(&mut self) -> Result; 20 | fn read_int32_var(&mut self) -> Result; 21 | } 22 | 23 | // reads values from a buffer 24 | impl WireReader for BitReader 25 | where T: std::io::Read 26 | { 27 | // read a little endian long from the stream 28 | fn read_long(&mut self) -> Result 29 | { 30 | Ok(self.read::(32)?) 31 | } 32 | 33 | // read a little endian longlong from the stream 34 | fn read_longlong(&mut self) -> Result 35 | { 36 | Ok(self.read::(64)?) 37 | } 38 | 39 | // read a little endian long from the stream 40 | fn read_word(&mut self) -> Result 41 | { 42 | Ok(self.read::(16)?) 43 | } 44 | 45 | // read a single byte from the stream 46 | fn read_char(&mut self) -> Result 47 | { 48 | Ok(self.read::(8)?) 49 | } 50 | 51 | // read an arbitrarily sized null terminated string 52 | fn read_string(&mut self) -> Result 53 | { 54 | // some reasonable space for small strings 55 | let mut buf: Vec = Vec::with_capacity(128); 56 | 57 | // not great performance here... I wish there was a better 58 | // way but we'll continue doing the "easy" method until perf 59 | // says it's too poor to use this 60 | loop { 61 | // read single byte, if null exit loop 62 | let byte = self.read_char()?; 63 | if byte == 0 64 | { 65 | break 66 | } 67 | 68 | // otherwise append byte and continue 69 | buf.push(byte); 70 | } 71 | 72 | // convert to utf-8 string (it's probably just ascii but rust wants us to use utf-8) 73 | let out_str = std::str::from_utf8(&buf[..])?; 74 | 75 | Ok(out_str.to_string()) 76 | } 77 | 78 | /// source engine variable length 32-bit int encoding 79 | fn read_int32_var(&mut self) -> Result 80 | { 81 | let mut data: u8; 82 | let mut res: u32 = 0; 83 | let mut count: u32 = 0; 84 | 85 | loop 86 | { 87 | // maximum encoded bytes 88 | if count == 5 { 89 | return Err(anyhow::anyhow!("Invalid varint32 encoding!")); 90 | } 91 | 92 | data = self.read_char()?; 93 | res |= ((data & 0x7F) as u32) << (7 * count); 94 | count += 1; 95 | if (data & 0x80) == 0 { 96 | break; 97 | } 98 | } 99 | 100 | Ok(res) 101 | } 102 | } 103 | 104 | // wrapper to write network data as source engine expects on the wire 105 | pub trait WireWriter 106 | { 107 | fn write_long(&mut self, num: u32) -> Result<()>; 108 | fn write_longlong(&mut self, num: u64) -> Result<()>; 109 | fn write_word(&mut self, num: u16) -> Result<()>; 110 | fn write_char(&mut self, num: u8) -> Result<()>; 111 | fn write_string(&mut self, s: &str) -> Result<()>; 112 | fn write_bit(&mut self, bit: bool) -> Result<()>; 113 | fn write_int32_var(&mut self, num: u32) -> Result<()>; 114 | } 115 | 116 | impl WireWriter for BitWriter 117 | where T: std::io::Write 118 | { 119 | // write little endian long 120 | #[inline] 121 | fn write_long(&mut self, num: u32) -> Result<()> 122 | { 123 | self.write(32, num)?; 124 | 125 | Ok(()) 126 | } 127 | 128 | // write little endian 64-bit longlong 129 | #[inline] 130 | fn write_longlong(&mut self, num: u64) -> Result<()> 131 | { 132 | self.write(64, num)?; 133 | 134 | Ok(()) 135 | } 136 | 137 | #[inline] 138 | fn write_word(&mut self, num: u16) -> Result<()> 139 | { 140 | self.write(16, num)?; 141 | 142 | Ok(()) 143 | } 144 | 145 | 146 | // write char 147 | #[inline] 148 | fn write_char(&mut self, num: u8) -> Result<()> 149 | { 150 | self.write(8, num)?; 151 | 152 | Ok(()) 153 | } 154 | 155 | // write a string with a null terminator 156 | #[inline] 157 | fn write_string(&mut self, s: &str) -> Result<()> 158 | { 159 | if s.len() > 0 160 | { 161 | // write string and null terminator 162 | self.write_bytes(s.as_bytes())?; 163 | } 164 | 165 | // write a null byte 166 | self.write(8, 0)?; 167 | 168 | Ok(()) 169 | } 170 | 171 | 172 | #[inline] 173 | fn write_bit(&mut self, bit: bool) -> Result<()> 174 | { 175 | Ok(self.write_bit(bit)?) 176 | } 177 | 178 | // source engine variable length 32-bit int encoding 179 | #[inline] 180 | fn write_int32_var(&mut self, mut data: u32) -> Result<()> 181 | { 182 | while data > 0x7F 183 | { 184 | self.write(8, (data & 0x7F) | 0x80)?; 185 | data >>= 7; 186 | } 187 | self.write(8, data & 0x7F)?; 188 | Ok(()) 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /src/source/gamelogic.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gbps/se-rust-client/cef7fb4f48b57c37a253f7e7b13ff9fda80d7a3a/src/source/gamelogic.rs -------------------------------------------------------------------------------- /src/source/ice.rs: -------------------------------------------------------------------------------- 1 | /* 2 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 3 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 4 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, 5 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 6 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 7 | */ 8 | 9 | // Implementation of the ICE cipher for Rust 10 | // http://www.darkside.com.au/ice/ice.c 11 | // C implementation written by Written by Matthew Kwan - July 1996 12 | 13 | use std::borrow::{BorrowMut, Borrow}; 14 | 15 | #[derive(Default)] 16 | struct IceSubKey { 17 | key: [u32; 3], 18 | } 19 | 20 | #[derive(Default)] 21 | struct IceKeyStruct { 22 | ik_size: usize, 23 | ik_rounds: usize, 24 | ik_sched: Vec 25 | } 26 | 27 | #[allow(non_upper_case_globals)] 28 | const ice_smod: &'static [&'static [u32]] = &[ 29 | &[333, 313, 505, 369], 30 | &[379, 375, 319, 391], 31 | &[361, 445, 451, 397], 32 | &[397, 425, 395, 505], 33 | ]; 34 | 35 | #[allow(non_upper_case_globals)] 36 | const ice_sxor: &'static [&'static [u32]] = &[ 37 | &[0x83, 0x85, 0x9b, 0xcd], 38 | &[0xcc, 0xa7, 0xad, 0x41], 39 | &[0x4b, 0x2e, 0xd4, 0x33], 40 | &[0xea, 0xcb, 0x2e, 0x04], 41 | ]; 42 | 43 | #[allow(non_upper_case_globals)] 44 | const ice_pbox: &'static [u32] = &[ 45 | 0x00000001, 0x00000080, 0x00000400, 0x00002000, 46 | 0x00080000, 0x00200000, 0x01000000, 0x40000000, 47 | 0x00000008, 0x00000020, 0x00000100, 0x00004000, 48 | 0x00010000, 0x00800000, 0x04000000, 0x20000000, 49 | 0x00000004, 0x00000010, 0x00000200, 0x00008000, 50 | 0x00020000, 0x00400000, 0x08000000, 0x10000000, 51 | 0x00000002, 0x00000040, 0x00000800, 0x00001000, 52 | 0x00040000, 0x00100000, 0x02000000, 0x80000000 53 | ]; 54 | 55 | #[allow(non_upper_case_globals)] 56 | const ice_keyrot: &'static [i32] = &[ 57 | 0, 1, 2, 3, 2, 1, 3, 0, 58 | 1, 3, 2, 0, 3, 1, 0, 2 59 | ]; 60 | 61 | #[allow(non_upper_case_globals)] 62 | const ice_keyrot2: &'static [i32] = &[ 63 | 1, 3, 2, 0, 3, 1, 0, 2 64 | ]; 65 | 66 | pub struct IceEncryption { 67 | ice_sbox: [[u32; 1024]; 4], 68 | ice_key: IceKeyStruct, 69 | } 70 | 71 | impl IceEncryption { 72 | /// Create a new re-usable IceEncryption object. `n` is selected based on the desired strength 73 | /// of the ICE algorithm. `key` must be a slice of at least `n*8` bytes. 74 | /// 75 | /// # Arguments 76 | /// 77 | /// * `n` - The parameter `n` specifying the strength of the encryption. See algorithm for 78 | /// details. 79 | /// * `key` - An encryption key to use for this object. Must be at least `n*8` bytes in size. 80 | pub fn new(n: usize, key: &[u8]) -> Self { 81 | assert_eq!(key.len(), n*8, "Ice key must be exactly {} bytes in length for n={}", n*8, n); 82 | 83 | let mut obj = Self{ 84 | ice_sbox: [[0; 1024]; 4], 85 | ice_key: IceEncryption::ice_key_create(n) 86 | }; 87 | 88 | obj.ice_sboxes_init(); 89 | 90 | IceEncryption::ice_key_set(obj.ice_key.borrow_mut(), key); 91 | 92 | return obj 93 | } 94 | 95 | 96 | /// Encrypt 8-bytes of plaintext 97 | /// 98 | /// # Arguments 99 | /// 100 | /// * `ptext` - A reference to 8-bytes of plaintext to encrypt 101 | /// * `ctext` - A reference to a mutable array of at least 8-bytes for ciphertext output 102 | pub fn encrypt(&self, ptext: &[u8], ctext: &mut [u8]) 103 | { 104 | let lr = self.encrypt_block_inplace_prepare(ptext); 105 | self.encrypt_block_inplace(lr, ctext); 106 | } 107 | 108 | /// Prepare to encrypt 8-bytes of ciphertext in-place. 109 | /// Pass the return value into `encrypt_inplace` to encrypt plaintext. 110 | /// Using this instead of `encrypt` allows for slice reference aliasing to encrypt a buffer 111 | /// in-place. 112 | /// 113 | /// # Arguments 114 | /// 115 | /// * `ptext` - A reference to 8-bytes of plaintext to encrypt 116 | fn encrypt_block_inplace_prepare(&self, ptext: &[u8]) -> (u32, u32) 117 | { 118 | let l = ((ptext[0] as u32) << 24) 119 | | ((ptext[1] as u32) << 16) 120 | | ((ptext[2] as u32) << 8) | (ptext[3] as u32); 121 | 122 | let r = ((ptext[4] as u32) << 24) 123 | | ((ptext[5] as u32) << 16) 124 | | ((ptext[6] as u32) << 8) | (ptext[7] as u32); 125 | 126 | return (l, r) 127 | } 128 | 129 | /// Encrypt 8-bytes of plaintext in-place. 130 | /// Pass the return value from `encrypt_block_inplace_prepare` to encrypt plaintext. 131 | /// Using this instead of `encrypt` allows for slice reference aliasing to encrypt a buffer 132 | /// in-place. 133 | /// 134 | /// # Arguments 135 | /// 136 | /// * `lr` - The result of a call to `encrypt_block_inplace_prepare` 137 | fn encrypt_block_inplace(&self, lr: (u32, u32), ctext: &mut [u8]) 138 | { 139 | let ik = &self.ice_key; 140 | let mut i: usize = 0; 141 | let (mut l, mut r) = lr; 142 | 143 | loop { 144 | if i >= ik.ik_rounds { 145 | break; 146 | } 147 | l ^= self.ice_f(r, ik.ik_sched[i].borrow()); 148 | r ^= self.ice_f(l, ik.ik_sched[i + 1].borrow()); 149 | 150 | i += 2; 151 | } 152 | 153 | i = 0; 154 | loop { 155 | if i >= 4 { 156 | break; 157 | } 158 | 159 | ctext[3 - i] = (r & 0xff) as u8; 160 | ctext[7 - i] = (l & 0xff) as u8; 161 | 162 | r >>= 8; 163 | l >>= 8; 164 | i += 1; 165 | } 166 | } 167 | 168 | /// Encrypt an 8-byte aligned buffer in-place. 169 | /// Panics if the buffer length is not divisible by 8. 170 | /// 171 | /// # Arguments 172 | /// 173 | /// * `buffer` - The buffer to encrypt in place. 174 | pub fn encrypt_buffer_inplace(&self, buffer: &mut [u8]) 175 | { 176 | assert_eq!(buffer.len() % 8, 0); 177 | 178 | let nblocks = buffer.len() / 8; 179 | 180 | // decrypt each block 181 | for i in 0..nblocks { 182 | // start of this block in bytes 183 | let start_pos = i*8; 184 | // end of this block in bytes 185 | let end_pos = (i+1)*8; 186 | 187 | let lr; 188 | { 189 | // reference to the full block to decrypt 190 | let block = &buffer[start_pos..end_pos]; 191 | lr = self.encrypt_block_inplace_prepare(block); 192 | } 193 | 194 | // scratch space to decrypt to 195 | let scratch_block = &mut buffer[start_pos..end_pos]; 196 | self.encrypt_block_inplace(lr, scratch_block); 197 | } 198 | } 199 | 200 | 201 | /// Decrypt 8-bytes of ciphertext 202 | /// 203 | /// # Arguments 204 | /// 205 | /// * `ctext` - A reference to 8-bytes of ciphertext to decrypt 206 | /// * `ptext` - A reference to a mutable array of at least 8-bytes for plaintext output 207 | pub fn decrypt(&self, ctext: &[u8], ptext: &mut [u8]) 208 | { 209 | let lr = self.decrypt_block_inplace_prepare(ctext); 210 | self.decrypt_block_inplace(lr, ptext); 211 | } 212 | 213 | /// Prepare to decrypt 8-bytes of ciphertext in-place. 214 | /// Pass the return value into decrypt_inplace to decrypt plaintext. 215 | /// Using this instead of `decrypt` allows for slice reference aliasing to decrypt a buffer 216 | /// in-place. 217 | /// 218 | /// # Arguments 219 | /// 220 | /// * `ctext` - A reference to 8-bytes of ciphertext to decrypt 221 | #[inline(always)] 222 | pub fn decrypt_block_inplace_prepare(&self, ctext: &[u8]) -> (u32, u32) 223 | { 224 | let l = ((ctext[0] as u32) << 24) 225 | | ((ctext[1] as u32) << 16) 226 | | ((ctext[2] as u32) << 8) | (ctext[3] as u32); 227 | 228 | let r = ((ctext[4] as u32) << 24) 229 | | ((ctext[5] as u32) << 16) 230 | | ((ctext[6] as u32) << 8) | (ctext[7] as u32); 231 | 232 | return (l, r) 233 | } 234 | 235 | /// Decrypt 8-bytes of ciphertext in-place. 236 | /// Pass the return value from `decrypt_block_inplace_prepare` to decrypt plaintext. 237 | /// Using this instead of `decrypt` allows for slice reference aliasing to decrypt a buffer 238 | /// in-place. 239 | /// 240 | /// # Arguments 241 | /// 242 | /// * `lr` - The result of a call to `decrypt_block_inplace_prepare` 243 | pub fn decrypt_block_inplace(&self, lr: (u32, u32), ptext: &mut [u8]) 244 | { 245 | let ik = &self.ice_key; 246 | let mut i: isize; 247 | 248 | let (mut l, mut r) = lr; 249 | 250 | i = (ik.ik_rounds as isize) - 1; 251 | loop { 252 | if i <= 0 { 253 | break; 254 | } 255 | l ^= self.ice_f(r, ik.ik_sched[i as usize].borrow()); 256 | r ^= self.ice_f(l, ik.ik_sched[(i - 1) as usize].borrow()); 257 | 258 | i -= 2; 259 | } 260 | 261 | let mut i: usize = 0; 262 | loop { 263 | if i >= 4 { 264 | break; 265 | } 266 | 267 | ptext[3 - i] = (r & 0xff) as u8; 268 | ptext[7 - i] = (l & 0xff) as u8; 269 | 270 | r >>= 8; 271 | l >>= 8; 272 | i += 1; 273 | } 274 | } 275 | 276 | /// Decrypt an 8-byte aligned buffer in-place. 277 | /// Panics if the buffer length is not divisible by 8. 278 | /// 279 | /// # Arguments 280 | /// 281 | /// * `buffer` - The buffer to decrypt in place. 282 | pub fn decrypt_buffer_inplace(&self, buffer: &mut [u8]) 283 | { 284 | assert_eq!(buffer.len() % 8, 0); 285 | 286 | let nblocks = buffer.len() / 8; 287 | 288 | // decrypt each block 289 | for i in 0..nblocks { 290 | // start of this block in bytes 291 | let start_pos = i*8; 292 | // end of this block in bytes 293 | let end_pos = (i+1)*8; 294 | 295 | let lr; 296 | { 297 | // reference to the full block to decrypt 298 | let block = &buffer[start_pos..end_pos]; 299 | lr = self.decrypt_block_inplace_prepare(block); 300 | } 301 | 302 | // slice of the block to decrypt to 303 | let target_block = &mut buffer[start_pos..end_pos]; 304 | self.decrypt_block_inplace(lr, target_block); 305 | } 306 | } 307 | 308 | fn gf_mult(mut a: u32, mut b: u32, m: u32) -> u32 { 309 | let mut res: u32 = 0; 310 | 311 | while b != 0 { 312 | if (b & 1) != 0 { 313 | res ^= a; 314 | } 315 | 316 | a <<= 1; 317 | b >>= 1; 318 | 319 | if a >= 256 { 320 | a ^= m; 321 | } 322 | } 323 | 324 | return res; 325 | } 326 | 327 | fn gf_exp7(b: u32, m: u32) -> u32 { 328 | let mut x: u32; 329 | 330 | if b == 0 { 331 | return 0; 332 | } 333 | 334 | x = IceEncryption::gf_mult(b, b, m); 335 | x = IceEncryption::gf_mult(b, x, m); 336 | x = IceEncryption::gf_mult(x, x, m); 337 | return IceEncryption::gf_mult(b, x, m); 338 | } 339 | 340 | fn ice_perm32(mut x: u32) -> u32 { 341 | let mut res: u32 = 0; 342 | let mut pbox_idx = 0; 343 | 344 | while x != 0 { 345 | if (x & 1) != 0 { 346 | res |= ice_pbox[pbox_idx]; 347 | } 348 | pbox_idx += 1; 349 | x >>= 1; 350 | } 351 | 352 | return res 353 | } 354 | 355 | fn ice_sboxes_init(&mut self) { 356 | for i in 0..1024 { 357 | let col: usize = (i >> 1) & 0xff; 358 | let row: usize = (i & 0x1) | ((i & 0x200) >> 8); 359 | let mut x: u32; 360 | 361 | x = IceEncryption::gf_exp7((col as u32) ^ ice_sxor[0][row], ice_smod[0][row] as u32) << 24; 362 | self.ice_sbox[0][i] = IceEncryption::ice_perm32 (x); 363 | 364 | x = IceEncryption::gf_exp7((col as u32) ^ ice_sxor[1][row], ice_smod[1][row] as u32) << 16; 365 | self.ice_sbox[1][i] = IceEncryption::ice_perm32 (x); 366 | 367 | x = IceEncryption::gf_exp7((col as u32) ^ ice_sxor[2][row], ice_smod[2][row] as u32) << 8; 368 | self.ice_sbox[2][i] = IceEncryption::ice_perm32 (x); 369 | 370 | x = IceEncryption::gf_exp7((col as u32) ^ ice_sxor[3][row], ice_smod[3][row] as u32); 371 | self.ice_sbox[3][i] = IceEncryption::ice_perm32 (x); 372 | } 373 | } 374 | 375 | fn ice_key_create(n: usize) -> IceKeyStruct { 376 | let mut ik: IceKeyStruct = Default::default(); 377 | 378 | if n < 1 { 379 | ik.ik_size = 1; 380 | ik.ik_rounds = 8; 381 | } else { 382 | ik.ik_size = n; 383 | ik.ik_rounds = n * 16; 384 | } 385 | 386 | ik.ik_sched = Vec::with_capacity(ik.ik_rounds); 387 | for _j in 0..ik.ik_rounds { 388 | ik.ik_sched.push(IceSubKey{ 389 | key: [0; 3] 390 | }) 391 | } 392 | 393 | 394 | return ik; 395 | } 396 | 397 | fn ice_f(&self, p: u32, sk: &IceSubKey) -> u32 { 398 | let tl: u32; 399 | let tr: u32; 400 | let mut al: u32; 401 | let mut ar: u32; 402 | 403 | tl = ((p >> 16) & 0x3ff) | (((p >> 14) | (p << 18)) & 0xffc00); 404 | 405 | /* Right half expansion */ 406 | tr = (p & 0x3ff) | ((p << 2) & 0xffc00); 407 | 408 | /* Perform the salt permutation */ 409 | /* al = (tr & sk[2]) | (tl & ~sk[2]); */ 410 | /* ar = (tl & sk[2]) | (tr & ~sk[2]); */ 411 | al = sk.key[2] & (tl ^ tr); 412 | ar = al ^ tr; 413 | al ^= tl; 414 | 415 | al ^= sk.key[0]; /* XOR with the subkey */ 416 | ar ^= sk.key[1]; 417 | 418 | /* S-box lookup and permutation */ 419 | return self.ice_sbox[0][(al >> 10) as usize] | self.ice_sbox[1][(al & 0x3ff) as usize] 420 | | self.ice_sbox[2][(ar >> 10) as usize] | self.ice_sbox[3][(ar & 0x3ff) as usize]; 421 | } 422 | 423 | fn ice_key_sched_build(ik: &mut IceKeyStruct, kb: &mut [u32], n: usize, keyrot: &[i32]) 424 | { 425 | for i in 0..8 { 426 | let kr: i32 = keyrot[i]; 427 | let isk = &mut ik.ik_sched[n + i]; 428 | 429 | for j in 0..3 { 430 | isk.key[j] = 0; 431 | } 432 | 433 | for j in 0..15 { 434 | let curr_sk = &mut isk.key[j % 3]; 435 | 436 | for k in 0..4 { 437 | let curr_kb = &mut kb[((kr + k) & 3) as usize]; 438 | let bit = *curr_kb & 1; 439 | 440 | *curr_sk = (*curr_sk << 1) | bit; 441 | *curr_kb = (*curr_kb >> 1) | ((bit ^ 1) << 15); 442 | } 443 | } 444 | } 445 | } 446 | 447 | fn ice_key_set(ik: &mut IceKeyStruct, key: &[u8]) 448 | { 449 | if ik.ik_rounds == 8 { 450 | let kb: &mut [u32] = &mut [0; 4]; 451 | 452 | for i in 0..4 { 453 | kb[3 - i] = ( (key[i*2] as u32) << 8) | (key[i*2 + 1] as u32); 454 | } 455 | 456 | IceEncryption::ice_key_sched_build(ik, kb, 0, ice_keyrot); 457 | return; 458 | } 459 | 460 | for i in 0..ik.ik_size { 461 | let kb: &mut [u32] = &mut [0; 4]; 462 | for j in 0..4 { 463 | kb[3 - j] = ( (key[i*8 + j*2] as u32) << 8) | (key[i*8 + j*2 + 1] as u32); 464 | } 465 | 466 | IceEncryption::ice_key_sched_build(ik, kb, i*8, ice_keyrot); 467 | IceEncryption::ice_key_sched_build(ik, kb, ik.ik_rounds - 8 - (i*8), ice_keyrot2); 468 | } 469 | } 470 | } 471 | 472 | #[test] 473 | fn test() { 474 | // n=2 test 475 | let plaintext = "BBBBBBBB"; 476 | let key = "AAAAAAAAAAAAAAAA"; 477 | let mut ctext = [0; 8]; 478 | let mut ptext = [0; 8]; 479 | 480 | // create the key 481 | let state = IceEncryption::new(2, key.as_bytes()); 482 | 483 | // encrypt the plaintext 484 | state.encrypt(plaintext.as_bytes(), &mut ctext); 485 | 486 | // ensure it's the proper ciphertext 487 | assert_eq!(ctext, [0xac, 0x87, 0x14, 0xe3, 0x22, 0x82, 0x56, 0x80]); 488 | 489 | // decrypt the plaintext 490 | state.decrypt(&ctext, &mut ptext); 491 | 492 | // ensure it matches the original plaintext 493 | assert_eq!(ptext, plaintext.as_bytes()); 494 | 495 | // n = 8 test 496 | let key = "kFc8zALkEPTgTyDTerPjnf8LZr7aLFs9G9tDdUQFYZzffAYVnz2VzyuJ5RQwc6uH"; 497 | let state = IceEncryption::new(8, key.as_bytes()); 498 | 499 | state.encrypt(plaintext.as_bytes(), &mut ctext); 500 | 501 | assert_eq!(ctext, [0xf1, 0x75, 0x76, 0xab, 0x4a, 0x61, 0x34, 0xd7]); 502 | 503 | state.decrypt(&ctext, &mut ptext); 504 | 505 | assert_eq!(ptext, plaintext.as_bytes()); 506 | } -------------------------------------------------------------------------------- /src/source/lzss.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::fmt; 3 | use byteorder::{ReadBytesExt, LittleEndian}; 4 | 5 | #[derive(Debug)] 6 | pub enum LzssError 7 | { 8 | InvalidHeader, 9 | BadData, 10 | SizeMismatch, 11 | IoError(std::io::Error), 12 | } 13 | 14 | impl From for LzssError 15 | { 16 | fn from(error: std::io::Error) -> LzssError 17 | { 18 | LzssError::IoError(error) 19 | } 20 | } 21 | 22 | impl fmt::Display for LzssError { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self 25 | { 26 | LzssError::InvalidHeader => write!(f, "Invalid header in LZSS compressed data"), 27 | LzssError::BadData => write!(f, "Invalid compressed data"), 28 | LzssError::SizeMismatch => write!(f, "Compressed data was not of expected size"), 29 | LzssError::IoError(_) => write!(f, "Reached EOF early"), 30 | } 31 | } 32 | } 33 | 34 | impl std::error::Error for LzssError { 35 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 36 | None 37 | } 38 | } 39 | 40 | pub struct Lzss 41 | { 42 | 43 | } 44 | 45 | const LZSS_HEADER: u32 = (('S' as u32)<<24) | (('S' as u32)<<16) | (('Z' as u32)<<8) | ('L' as u32); 46 | 47 | impl Lzss 48 | { 49 | pub fn decode(mut input: &[u8]) -> Result, LzssError> 50 | { 51 | // ensure proper LZSS header 52 | let header: u32 = input.read_u32::()?; 53 | if header != LZSS_HEADER { 54 | return Err(LzssError::InvalidHeader); 55 | } 56 | 57 | // get the supposed "actual size" to verify at the end 58 | let actual_size: usize = input.read_u32::()? as usize; 59 | 60 | // pre-allocate the actual size (errors if we go over this) 61 | let mut output: Vec = Vec::with_capacity(actual_size); 62 | 63 | // keep track of beginning and end of the vector as pointers for raw ptr writes 64 | let mut out_ptr = output.as_mut_ptr(); 65 | let out_ptr_end: *mut u8; 66 | unsafe { 67 | out_ptr_end = out_ptr.add(output.capacity()) 68 | } 69 | 70 | let mut get_cmd_byte: u8 = 0; 71 | let mut cmd_byte: u8 = 0; 72 | loop { 73 | // is it time to read a new command byte? 74 | if get_cmd_byte == 0 { 75 | cmd_byte = input.read_u8()?; 76 | } 77 | 78 | // read a command byte every 8 bytes 79 | get_cmd_byte = (get_cmd_byte + 1) & 0x07; 80 | 81 | // if this is a command byte? 82 | if (cmd_byte & 1) != 0 { 83 | let pos_byte: usize = input.read_u8()? as usize; 84 | 85 | // the position of the reference 86 | let mut position = pos_byte << 4; 87 | 88 | // the size of the reference 89 | let count_byte: usize = input.read_u8()? as usize; 90 | 91 | position |= count_byte >> 4; 92 | 93 | let count = (count_byte & 0xF) + 1; 94 | 95 | // count == 0 is the end 96 | if count == 1 97 | { 98 | break; 99 | } 100 | 101 | // calculate range of the copy from the previously uncompressed data 102 | let target_index = (output.len() - 1) - position; 103 | let target_index_end = target_index + count; 104 | 105 | // copy the reference into output, bytewise since we can't assume 106 | // a full memcpy due to overlap 107 | for idx in target_index..target_index_end 108 | { 109 | // check for bad access 110 | if idx >= output.len(){ 111 | return Err(LzssError::BadData); 112 | } 113 | 114 | output.push(output[idx]); 115 | 116 | // keep incrementing output pointer for new item 117 | unsafe { 118 | out_ptr = out_ptr.add(1); 119 | } 120 | } 121 | } else { 122 | // otherwise, this is not a command byte but a regular byte of data 123 | // copy it to output 124 | 125 | // check for writing past bounds 126 | if out_ptr == out_ptr_end 127 | { 128 | break; 129 | } 130 | 131 | // hot path for copying non-compressed bytes to output 132 | // instead of using output.push. We do raw pointer read/write 133 | // which increases perf of this loop by up to 4x due to 134 | // lack of bounds checking and error handling 135 | unsafe { 136 | // read input byte and shift input slice 137 | let byt = input.as_ptr().read(); 138 | input = &input[1..]; 139 | 140 | // write byte to output 141 | std::ptr::write(out_ptr, byt); 142 | 143 | // increment output pointer for next write 144 | out_ptr = out_ptr.add(1); 145 | 146 | // increase length of vector by 1 more item 147 | output.set_len(output.len() + 1); 148 | } 149 | } 150 | 151 | cmd_byte >>= 1; 152 | } 153 | 154 | // ensure it's the size we expected 155 | if output.len() != actual_size 156 | { 157 | return Err(LzssError::SizeMismatch); 158 | } 159 | 160 | // all good, return the output 161 | Ok(output) 162 | } 163 | } -------------------------------------------------------------------------------- /src/source/mod.rs: -------------------------------------------------------------------------------- 1 | mod subchannel; 2 | mod channel; 3 | mod packetbase; 4 | mod bitbuf; 5 | pub mod protos; 6 | pub mod packets; 7 | pub mod ice; 8 | pub mod lzss; 9 | pub mod netmessages; 10 | pub use channel::*; 11 | pub use packetbase::*; 12 | -------------------------------------------------------------------------------- /src/source/netmessages.rs: -------------------------------------------------------------------------------- 1 | use crate::protoutil; 2 | use crate::source::bitbuf::{WireWriter}; 3 | use bitstream_io::{BitWriter, LittleEndian}; 4 | use smallvec::{SmallVec}; 5 | use crate::source::protos::*; 6 | use ::protobuf::ProtobufEnum; 7 | use NET_Messages::*; 8 | use SVC_Messages::*; 9 | 10 | type ProtoMessage = Box; 11 | 12 | // a netmessage packet, either to be sent or received from the network 13 | pub struct NetMessage 14 | { 15 | // the netmessage enum identifier for this message 16 | id: i32, 17 | 18 | // the size of this encoded message 19 | size: u32, 20 | 21 | // the internal protobuf message for this netmessage 22 | message: ProtoMessage, 23 | } 24 | 25 | impl NetMessage 26 | { 27 | // Decode a netmessage into a NetMessage object 28 | pub fn bind(id: i32, buffer: &[u8]) -> anyhow::Result 29 | { 30 | let net_enum = NET_Messages::from_i32(id as i32); 31 | if net_enum.is_some() 32 | { 33 | return match net_enum.unwrap() 34 | { 35 | net_NOP => Self::from_buffer::(buffer, id), 36 | net_Disconnect => Self::from_buffer::(buffer, id), 37 | net_File => Self::from_buffer::(buffer, id), 38 | net_SplitScreenUser => Self::from_buffer::(buffer, id), 39 | net_Tick => Self::from_buffer::(buffer, id), 40 | net_StringCmd => Self::from_buffer::(buffer, id), 41 | net_SetConVar => Self::from_buffer::(buffer, id), 42 | net_SignonState => Self::from_buffer::(buffer, id), 43 | net_PlayerAvatarData => Self::from_buffer::(buffer, id), 44 | } 45 | } 46 | 47 | let svc_enum = SVC_Messages::from_i32(id as i32); 48 | if svc_enum.is_some() 49 | { 50 | return match svc_enum.unwrap() 51 | { 52 | svc_ServerInfo => Self::from_buffer::(buffer, id), // first message from server about game; map etc 53 | svc_SendTable => Self::from_buffer::(buffer, id), // sends a sendtable description for a game class 54 | svc_ClassInfo => Self::from_buffer::(buffer, id), // Info about classes (first byte is a CLASSINFO_ define). 55 | svc_SetPause => Self::from_buffer::(buffer, id), // tells client if server paused or unpaused 56 | svc_CreateStringTable => Self::from_buffer::(buffer, id), // inits shared string tables 57 | svc_UpdateStringTable => Self::from_buffer::(buffer, id), // updates a string table 58 | svc_VoiceInit => Self::from_buffer::(buffer, id), // inits used voice codecs & quality 59 | svc_VoiceData => Self::from_buffer::(buffer, id), // Voicestream data from the server 60 | svc_Print => Self::from_buffer::(buffer, id), // print text to console 61 | svc_Sounds => Self::from_buffer::(buffer, id), // starts playing sound 62 | svc_SetView => Self::from_buffer::(buffer, id), // sets entity as point of view 63 | svc_FixAngle => Self::from_buffer::(buffer, id), // sets/corrects players viewangle 64 | svc_CrosshairAngle => Self::from_buffer::(buffer, id), // adjusts crosshair in auto aim mode to lock on traget 65 | svc_BSPDecal => Self::from_buffer::(buffer, id), // add a static decal to the world BSP 66 | svc_SplitScreen => Self::from_buffer::(buffer, id), // split screen style message 67 | svc_UserMessage => Self::from_buffer::(buffer, id), // a game specific message 68 | svc_EntityMessage => Self::from_buffer::(buffer, id), // a message for an entity 69 | svc_GameEvent => Self::from_buffer::(buffer, id), // global game event fired 70 | svc_PacketEntities => Self::from_buffer::(buffer, id), // non-delta compressed entities 71 | svc_TempEntities => Self::from_buffer::(buffer, id), // non-reliable event object 72 | svc_Prefetch => Self::from_buffer::(buffer, id), // only sound indices for now 73 | svc_Menu => Self::from_buffer::(buffer, id), // display a menu from a plugin 74 | svc_GameEventList => Self::from_buffer::(buffer, id), // list of known games events and fields 75 | svc_GetCvarValue => Self::from_buffer::(buffer, id), // Server wants to know the value of a cvar on the client 76 | svc_PaintmapData => Self::from_buffer::(buffer, id), 77 | svc_CmdKeyValues => Self::from_buffer::(buffer, id), // Server submits KeyValues command for the client 78 | svc_EncryptedData => Self::from_buffer::(buffer, id), 79 | svc_HltvReplay => Self::from_buffer::(buffer, id), 80 | svc_Broadcast_Command => Self::from_buffer::(buffer, id), 81 | } 82 | } 83 | 84 | Err(anyhow::anyhow!("Unknown netmessage id {}!", id)) 85 | } 86 | 87 | // get the inner proto message 88 | pub fn inner(&self) -> &ProtoMessage 89 | { 90 | return &self.message; 91 | } 92 | 93 | // get the maximum size of the encoded message with the header 94 | pub fn get_max_size(&self) -> usize 95 | { 96 | return (self.size + 8) as usize; 97 | } 98 | 99 | // write the netmessage (with header) to a vector, clears the vector beforehand 100 | pub fn encode_to_buffer(&mut self, buf: &mut Vec) -> anyhow::Result<()> 101 | { 102 | // TODO: Encode message directly to buf 103 | 104 | // create a stack/heap allocated buffer hopefully to optimize small netmessage encodings 105 | let mut encode_buf: SmallVec<[u8; 2048]> = SmallVec::with_capacity(self.size as usize); 106 | 107 | let cursor = std::io::Cursor::new(buf); 108 | let mut writer = BitWriter::endian(cursor, LittleEndian); 109 | 110 | // encode the proto message 111 | self.message.write_to_writer(&mut encode_buf)?; 112 | 113 | // write the netmessage header and proto message 114 | writer.write_int32_var(self.id as u32)?; 115 | writer.write_int32_var(self.size)?; 116 | writer.write_bytes(&encode_buf)?; 117 | 118 | Ok(()) 119 | } 120 | 121 | // create a message to send from a proto message and the id of the message 122 | pub fn from_proto(message: ProtoMessage, id: i32) -> Self { 123 | NetMessage{ 124 | id, 125 | size: message.compute_size(), 126 | message, 127 | } 128 | } 129 | 130 | // create a message from a network buffer 131 | pub fn from_buffer(message: &[u8], id: i32) -> anyhow::Result 132 | where M: ::protobuf::Message 133 | { 134 | let msg: ProtoMessage = Box::new(protoutil::deserialize::(message)?); 135 | 136 | Ok(NetMessage{ 137 | id, 138 | size: msg.get_cached_size(), 139 | message: msg, 140 | }) 141 | } 142 | 143 | // get the type name of this netmessage 144 | pub fn get_type_name(&self) -> &'static str 145 | { 146 | let net_enum = NET_Messages::from_i32(self.id); 147 | if net_enum.is_some() 148 | { 149 | return net_enum.unwrap().descriptor().name(); 150 | } 151 | 152 | let svc_enum = SVC_Messages::from_i32(self.id); 153 | if svc_enum.is_some() 154 | { 155 | return svc_enum.unwrap().descriptor().name(); 156 | } 157 | 158 | return ""; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/source/packetbase.rs: -------------------------------------------------------------------------------- 1 | use super::channel::*; 2 | use anyhow::Result; 3 | use super::packets::*; 4 | use super::bitbuf::*; 5 | 6 | #[allow(non_camel_case_types)] 7 | #[repr(u8)] 8 | #[derive(Debug, PartialEq)] 9 | pub enum ConnectionlessPacketType 10 | { 11 | Invalid = 0 as u8, 12 | A2A_ACK = 106 as u8, 13 | A2A_PING = 105 as u8, 14 | A2S_INFO = 84 as u8, 15 | S2A_INFO_SRC = 73 as u8, 16 | A2S_GETCHALLENGE = 113 as u8, 17 | S2C_CHALLENGE = 65 as u8, 18 | C2S_CONNECT = 107 as u8, 19 | S2C_CONNECTION = 66 as u8, 20 | } 21 | 22 | impl From for ConnectionlessPacketType 23 | { 24 | fn from(x: u8) -> ConnectionlessPacketType 25 | { 26 | match x 27 | { 28 | 106 => ConnectionlessPacketType::A2A_ACK, 29 | 105 => ConnectionlessPacketType::A2A_PING, 30 | 84 => ConnectionlessPacketType::A2S_INFO, 31 | 73 => ConnectionlessPacketType::S2A_INFO_SRC, 32 | 113 => ConnectionlessPacketType::A2S_GETCHALLENGE, 33 | 65 => ConnectionlessPacketType::S2C_CHALLENGE, 34 | 107 => ConnectionlessPacketType::C2S_CONNECT, 35 | 66 => ConnectionlessPacketType::S2C_CONNECTION, 36 | _ => ConnectionlessPacketType::Invalid 37 | } 38 | } 39 | } 40 | 41 | 42 | #[allow(non_camel_case_types)] 43 | #[enum_dispatch] 44 | #[derive(Debug)] 45 | pub enum ConnectionlessPacket 46 | { 47 | A2aAck, 48 | A2aPing, 49 | A2sInfo, 50 | S2aInfoSrc, 51 | A2sGetChallenge, 52 | S2cChallenge, 53 | C2sConnect, 54 | S2cConnection, 55 | } 56 | 57 | impl ConnectionlessPacket 58 | { 59 | // get the type enum from a packet 60 | pub fn get_type(&self) -> ConnectionlessPacketType 61 | { 62 | match self 63 | { 64 | ConnectionlessPacket::A2aAck(_) => ConnectionlessPacketType::A2A_ACK, 65 | ConnectionlessPacket::A2aPing(_) => ConnectionlessPacketType::A2A_PING, 66 | ConnectionlessPacket::A2sInfo(_) => ConnectionlessPacketType::A2S_INFO, 67 | ConnectionlessPacket::S2aInfoSrc(_) => ConnectionlessPacketType::S2A_INFO_SRC, 68 | ConnectionlessPacket::A2sGetChallenge(_) => ConnectionlessPacketType::A2S_GETCHALLENGE, 69 | ConnectionlessPacket::S2cChallenge(_) => ConnectionlessPacketType::S2C_CHALLENGE, 70 | ConnectionlessPacket::C2sConnect(_) => ConnectionlessPacketType::C2S_CONNECT, 71 | ConnectionlessPacket::S2cConnection(_) => ConnectionlessPacketType::S2C_CONNECTION, 72 | } 73 | } 74 | 75 | // serialize the packet to a byte array 76 | fn serialize_header(&self, target: &mut BitBufWriterType) -> Result<()> 77 | { 78 | // SE determines netchannel vs. connectionless by the header 79 | target.write_long(CONNECTIONLESS_HEADER)?; 80 | 81 | // next is the id of the connectionless packet 82 | target.write_char(self.get_type() as u8)?; 83 | 84 | Ok(()) 85 | } 86 | 87 | // serialize the packet to a channel 88 | pub fn serialize_to_channel(&self, target: &mut BufUdp) -> Result<()> 89 | { 90 | { 91 | let scratch_space = target.get_scratch_mut(); 92 | 93 | // reset length ptr 94 | scratch_space.clear(); 95 | 96 | // scratch space to serialize packet 97 | let mut scratch: BitBufWriterType = BitWriter::endian(std::io::Cursor::new(scratch_space), LittleEndian); 98 | 99 | // serialize to scratch space 100 | self.serialize_header(&mut scratch)?; 101 | self.serialize_values(&mut scratch)?; 102 | } 103 | 104 | // send over channel 105 | target.send_raw(&target.get_scratch()[..])?; 106 | 107 | Ok(()) 108 | } 109 | } 110 | 111 | pub const CONNECTIONLESS_HEADER: u32 = 0xFFFFFFFF; 112 | 113 | #[enum_dispatch(ConnectionlessPacket)] 114 | pub trait ConnectionlessPacketTrait 115 | { 116 | // serialize extra packet information 117 | fn serialize_values(&self, _target: &mut BitBufWriterType) -> Result<()> 118 | { 119 | // to be overridden 120 | Ok(()) 121 | } 122 | } 123 | 124 | // A packet we are allowed to receive from the network 125 | pub trait ConnectionlessPacketReceive: Sized 126 | { 127 | fn get_type() -> ConnectionlessPacketType; 128 | 129 | // serialize extra packet information 130 | fn read_values(packet: &mut BitBufReaderType) -> Result; 131 | } 132 | -------------------------------------------------------------------------------- /src/source/packets.rs: -------------------------------------------------------------------------------- 1 | use super::packetbase::ConnectionlessPacketTrait; 2 | use super::packetbase::ConnectionlessPacketReceive; 3 | 4 | use anyhow::Result; 5 | use num_traits::{FromPrimitive, ToPrimitive}; 6 | use crate::source::ConnectionlessPacketType; 7 | use super::bitbuf::*; 8 | 9 | use super::protos::CCLCMsg_SplitPlayerConnect; 10 | use protobuf::Message; 11 | 12 | #[derive(Debug)] 13 | pub struct A2aAck {} 14 | impl ConnectionlessPacketTrait for A2aAck 15 | { 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct A2aPing {} 20 | impl ConnectionlessPacketTrait for A2aPing 21 | { 22 | } 23 | 24 | #[derive(Debug, Default)] 25 | pub struct A2sInfo {} 26 | impl ConnectionlessPacketTrait for A2sInfo 27 | { 28 | fn serialize_values(&self, target: &mut BitBufWriterType) -> Result<()> 29 | { 30 | // write other header info 31 | target.write_string("Source Engine Query")?; 32 | 33 | Ok(()) 34 | } 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct S2aInfoSrc { 39 | protocol_num: u8, 40 | host_name: String, 41 | map_name: String, 42 | mod_name: String, 43 | game_name: String, 44 | app_id: u16, 45 | num_players: u8, 46 | max_players: u8, 47 | num_bots: u8, 48 | dedicated_or_listen: u8, // 'd' = dedicated, 'l' = listen 49 | host_os: u8, // 'w' == windows, 'm' == macos, 'l' == linux 50 | has_password: u8, 51 | is_secure: u8, 52 | host_version_string: String, 53 | } 54 | impl ConnectionlessPacketTrait for S2aInfoSrc 55 | { 56 | } 57 | 58 | impl ConnectionlessPacketReceive for S2aInfoSrc 59 | { 60 | fn get_type() -> ConnectionlessPacketType 61 | { 62 | ConnectionlessPacketType::S2A_INFO_SRC 63 | } 64 | 65 | fn read_values(packet: &mut BitBufReaderType) -> Result 66 | { 67 | Ok(S2aInfoSrc{ 68 | protocol_num: packet.read_char()?, 69 | host_name: packet.read_string()?, 70 | map_name: packet.read_string()?, 71 | mod_name: packet.read_string()?, 72 | game_name: packet.read_string()?, 73 | app_id: packet.read_word()?, 74 | num_players: packet.read_char()?, 75 | max_players: packet.read_char()?, 76 | num_bots: packet.read_char()?, 77 | dedicated_or_listen: packet.read_char()?, 78 | host_os: packet.read_char()?, 79 | has_password: packet.read_char()?, 80 | is_secure: packet.read_char()?, 81 | host_version_string: packet.read_string()?, 82 | }) 83 | } 84 | } 85 | 86 | // client requests challenge with server 87 | #[derive(Debug)] 88 | pub struct A2sGetChallenge 89 | { 90 | // the "type" of challenge 91 | // normally in the form of "connect0xAABBCCDD" 92 | // where "connect0x00000000" is a perfectly valid conection string 93 | connect_string: String 94 | } 95 | impl ConnectionlessPacketTrait for A2sGetChallenge 96 | { 97 | fn serialize_values(&self, target: &mut BitBufWriterType) -> Result<()> 98 | { 99 | // write other header info 100 | target.write_string(&self.connect_string)?; 101 | 102 | Ok(()) 103 | } 104 | } 105 | 106 | impl Default for A2sGetChallenge 107 | { 108 | // set the default challenge connect string 109 | fn default() -> A2sGetChallenge 110 | { 111 | A2sGetChallenge{ 112 | connect_string: String::from("connect0x00000000") 113 | } 114 | } 115 | } 116 | 117 | impl A2sGetChallenge 118 | { 119 | // create a challenge for a specific cookie 120 | pub fn with_challenge(cookie_value: u32) -> A2sGetChallenge 121 | { 122 | A2sGetChallenge { 123 | connect_string: format!("connect{:#010x}", cookie_value) 124 | } 125 | } 126 | } 127 | 128 | #[derive(FromPrimitive, ToPrimitive, Debug)] 129 | #[allow(non_camel_case_types)] 130 | #[repr(u32)] 131 | pub enum AuthProtocolType 132 | { 133 | PROTOCOL_UNUSED = 0x01, // unused 134 | PROTOCOL_HASHEDCDKEY = 0x02, // only for misconfigured listen servers 135 | PROTOCOL_STEAM = 0x03, // auth with steam, default 136 | } 137 | 138 | // server responds to challenge with additional server info 139 | #[derive(Debug)] 140 | pub struct S2cChallenge 141 | { 142 | pub challenge_num: u32, // randomly generated challenge for this client 143 | pub auth_protocol: AuthProtocolType, // PROTOCOL_STEAM only 144 | pub steam2_encryption_enabled: u16, // 0 nowadays 145 | pub gameserver_steamid: u64, // gameserver's steamid 146 | pub vac_secured: u8, // 0 or 1 147 | pub context_response: String, // should be "connect0x...." on success, otherwise "connect-retry" 148 | pub host_version: u32, //server host version 149 | pub lobby_type: String, // "", "friends", or "public" 150 | pub password_required: u8, // 1 if password is required to connect 151 | pub lobby_id: u64, // -1 unless lobby matching is used 152 | pub friends_required: u8, // 0, unless lobby matching is used 153 | pub valve_ds: u8, // 1 if this is a valve hosted dedicated server 154 | pub require_certificate: u8, // 0, unless certificate authentication is used 155 | /* TODO: Certificate Authentication */ 156 | } 157 | impl ConnectionlessPacketTrait for S2cChallenge {} 158 | impl ConnectionlessPacketReceive for S2cChallenge 159 | { 160 | fn get_type() -> ConnectionlessPacketType 161 | { 162 | ConnectionlessPacketType::S2C_CHALLENGE 163 | } 164 | 165 | fn read_values(packet: &mut BitBufReaderType) -> Result 166 | { 167 | Ok(S2cChallenge { 168 | challenge_num: packet.read_long()?, 169 | auth_protocol: FromPrimitive::from_u32(packet.read_long()?).ok_or(anyhow::anyhow!("Invalid auth protocol"))?, 170 | steam2_encryption_enabled: packet.read_word()?, 171 | gameserver_steamid: packet.read_longlong()?, 172 | vac_secured: packet.read_char()?, 173 | context_response: packet.read_string()?, 174 | host_version: packet.read_long()?, 175 | lobby_type: packet.read_string()?, 176 | password_required: packet.read_char()?, 177 | lobby_id: packet.read_longlong()?, 178 | friends_required: packet.read_char()?, 179 | valve_ds: packet.read_char()?, 180 | require_certificate: packet.read_char()?, 181 | }) 182 | } 183 | } 184 | 185 | impl S2cChallenge 186 | { 187 | // if true, the client should retry with the given cookie value 188 | pub fn should_retry(&self) -> bool 189 | { 190 | self.context_response == "connect-retry" 191 | } 192 | } 193 | 194 | #[derive(FromPrimitive, ToPrimitive, Debug)] 195 | pub enum CrossplayPlatform 196 | { 197 | Unknown, 198 | Pc, 199 | X360, 200 | Ps3 201 | } 202 | 203 | #[derive(Debug)] 204 | pub struct SteamAuthInfo 205 | { 206 | pub steamid: u64, 207 | pub auth_ticket: Vec, 208 | } 209 | 210 | #[derive(Debug)] 211 | pub struct C2sConnect 212 | { 213 | pub host_version: u32, 214 | pub auth_protocol: AuthProtocolType, 215 | pub challenge_num: u32, 216 | pub player_name: String, 217 | pub server_password: String, 218 | pub num_players: u8, 219 | pub split_player_connect: Vec, 220 | pub low_violence: bool, 221 | pub lobby_cookie: u64, 222 | pub crossplay_platform: CrossplayPlatform, 223 | pub encryption_key_index: u32, 224 | pub auth_info: SteamAuthInfo, 225 | } 226 | 227 | impl ConnectionlessPacketTrait for C2sConnect 228 | { 229 | fn serialize_values(&self, target: &mut BitBufWriterType) -> Result<()> 230 | { 231 | // write fields 232 | target.write_long(self.host_version)?; 233 | target.write_long(ToPrimitive::to_u32(&self.auth_protocol).ok_or(anyhow::anyhow!("Invalid auth protocol"))?)?; 234 | target.write_long(self.challenge_num)?; 235 | target.write_string(&self.player_name)?; 236 | target.write_string(&self.server_password)?; 237 | target.write_char(self.num_players)?; 238 | 239 | for player_num in 0..self.num_players 240 | { 241 | // netmessage number, not used 242 | target.write_int32_var(0)?; 243 | 244 | let encoded = self.split_player_connect[player_num as usize].write_to_bytes()?; 245 | target.write_int32_var(encoded.len() as u32)?; 246 | target.write_bytes(&encoded)?; 247 | } 248 | 249 | // more fields 250 | target.write_bit(self.low_violence)?; 251 | target.write_longlong(self.lobby_cookie)?; 252 | target.write_char(ToPrimitive::to_u8(&self.crossplay_platform).ok_or(anyhow::anyhow!("Invalid crossplay platform"))?)?; 253 | target.write_long(self.encryption_key_index)?; 254 | 255 | // auth info fields 256 | target.write_word((self.auth_info.auth_ticket.len() as u16)+8)?; 257 | target.write_longlong(self.auth_info.steamid)?; 258 | target.write_bytes(&self.auth_info.auth_ticket)?; 259 | 260 | // what genius though "oh, let's use a single bit to represent 261 | // low_violence and just leave this entire thing unaligned to a single byte... 262 | for _i in 0..7 { 263 | target.write_bit(false)?; 264 | } 265 | Ok(()) 266 | } 267 | } 268 | 269 | // server responds to challenge with additional server info 270 | #[derive(Debug)] 271 | pub struct S2cConnection 272 | { 273 | // connection string (largely unused, contains a single often unused encryption key index value) 274 | connection_string: String, 275 | } 276 | 277 | impl ConnectionlessPacketTrait for S2cConnection {} 278 | impl ConnectionlessPacketReceive for S2cConnection 279 | { 280 | fn get_type() -> ConnectionlessPacketType 281 | { 282 | ConnectionlessPacketType::S2C_CONNECTION 283 | } 284 | 285 | fn read_values(packet: &mut BitBufReaderType) -> Result 286 | { 287 | Ok(S2cConnection { 288 | connection_string: packet.read_string()?, 289 | }) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/source/protos/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod netmessages; 2 | pub use netmessages::*; -------------------------------------------------------------------------------- /src/source/subchannel.rs: -------------------------------------------------------------------------------- 1 | use bitstream_io::{BitReader, LittleEndian}; 2 | use crate::source::bitbuf::WireReader; 3 | use log::{warn, trace}; 4 | use crate::source::lzss::Lzss; 5 | use pretty_hex::PrettyHex; 6 | 7 | const MAX_FILE_SIZE: usize = (1<<26) - 1; 8 | const FRAGMENT_SIZE: usize = 1<<8; 9 | 10 | pub enum SubchannelStreamType 11 | { 12 | // reliable messages 13 | Message = 0, 14 | 15 | // file transfers 16 | File = 1, 17 | 18 | // invalid subchannel type 19 | Invalid = 2, 20 | } 21 | 22 | impl From for SubchannelStreamType 23 | { 24 | fn from(num: u8) -> Self 25 | { 26 | match num { 27 | 0 => SubchannelStreamType::Message, 28 | 1 => SubchannelStreamType::File, 29 | _ => SubchannelStreamType::Invalid, 30 | } 31 | } 32 | } 33 | 34 | // compressed fragment information, if compressed 35 | struct CompressedFragments { 36 | // size of payload when uncompressed, if it is compressed 37 | uncompressed_size: usize 38 | } 39 | 40 | // a file payload being sent 41 | struct FileFragments { 42 | // filename for the incoming file 43 | filename: String, 44 | 45 | // transfer id of this file 46 | transfer_id: u32, 47 | } 48 | 49 | // a current in-progress transfer 50 | pub struct TransferBuffer { 51 | // the buffer holding current transfer data 52 | buffer: Vec, 53 | 54 | // the number of fragments in this transfer 55 | num_fragments: usize, 56 | 57 | // number of acknowledged fragments 58 | num_fragments_ack: usize, 59 | } 60 | 61 | pub struct SubChannel 62 | { 63 | // file information if the payload is a file 64 | file: Option, 65 | 66 | // compression information if the payload is compressed 67 | compressed: Option, 68 | 69 | // if this payload is a replay? 70 | is_replay: bool, 71 | 72 | // the size of total bytes being sent over the network 73 | payload_size: usize, 74 | 75 | // current in-progress transfer 76 | transfer: Option, 77 | 78 | // contains the reliable state for this SubChannel 79 | // reliable state is a bit which flips back and forth acknowledging 80 | // transfers as they are received, shifted by the SubChannel index 81 | in_reliable_state: bool, 82 | } 83 | 84 | impl TransferBuffer { 85 | // create a new transfer buffer to receive incoming data 86 | fn new(transfer_size: usize) -> Self { 87 | 88 | // calculate the number of fragments that payload actually is 89 | // convert from bytes to fragments 90 | let num_fragments: usize = (transfer_size+FRAGMENT_SIZE-1)/(FRAGMENT_SIZE); 91 | trace!("Transfer size = {}, therefore allocating for {} fragments @ {} bytes per fragment", transfer_size, num_fragments, FRAGMENT_SIZE); 92 | 93 | // 94 | // allocate space for the entire payload 95 | let buffer = vec![0; transfer_size]; 96 | return TransferBuffer{ 97 | buffer, 98 | num_fragments, 99 | num_fragments_ack: 0, 100 | } 101 | } 102 | 103 | // read a given number of fragments over the network 104 | // return Ok(true) when the transfer is complete 105 | fn read_fragments(&mut self, start_frag: usize, num_fragments: usize, reader: &mut BitReader) -> anyhow::Result 106 | where T: std::io::Read 107 | { 108 | // total number of bytes to receive off of the network 109 | let mut total_recv_length: usize = num_fragments * FRAGMENT_SIZE; 110 | let last_recv_fragment = start_frag+num_fragments; 111 | let total_fragments_in_payload = self.num_fragments; 112 | let mut transfer_complete = false; 113 | 114 | trace!("[read_fragments] start_frag: {}, num_fragments: {}", start_frag, num_fragments); 115 | trace!("[read_fragments] total_recv_length: {}, total_fragments_in_payload: {}", total_recv_length, total_fragments_in_payload); 116 | 117 | // is this the last fragment? 118 | if last_recv_fragment == total_fragments_in_payload 119 | { 120 | // this is the last fragment, adjust the receiving length so that we only receive 121 | // the bytes of the final fragment that we want to finish this off 122 | let final_part = FRAGMENT_SIZE - ( self.buffer.len() % FRAGMENT_SIZE ); 123 | if final_part < FRAGMENT_SIZE 124 | { 125 | total_recv_length -= final_part; 126 | } 127 | 128 | transfer_complete = true; 129 | trace!("[read_fragments] Completing transfer! (final_part={})", final_part); 130 | } 131 | else if last_recv_fragment > total_fragments_in_payload 132 | { 133 | warn!("[read_fragments] Out of bounds fragment chunk!"); 134 | 135 | // does this fragment exceed the total size of the payload? 136 | return Err(anyhow::anyhow!("Fragment chunk received out of bounds")) 137 | } 138 | 139 | // start bytes for where to read in the buffer 140 | let start = start_frag * FRAGMENT_SIZE; 141 | 142 | trace!("[read_fragments] buffer[start..end] = buffer[{}..{}]", start, start+total_recv_length); 143 | 144 | // receive the bytes on the network 145 | reader.read_bytes(&mut self.buffer[start..(start+total_recv_length)])?; 146 | 147 | // acknowledge these packets 148 | self.num_fragments_ack += num_fragments; 149 | 150 | trace!("[read_fragments] Transfer status: [{}/{}]", self.num_fragments_ack, self.num_fragments); 151 | 152 | // have we finished this transfer entirely? 153 | if transfer_complete 154 | { 155 | trace!("Transfer complete."); 156 | return Ok(true) 157 | } 158 | 159 | // transfer not complete yet 160 | return Ok(false) 161 | } 162 | 163 | // decompress an LZSS payload and replace the buffer with the decompressed one on success 164 | fn decompress_payload(&mut self, expected_length: usize) -> anyhow::Result<()> 165 | { 166 | trace!("Payload BEFORE decompress (len={}):\n{:?}", self.buffer.len(), self.buffer.hex_dump()); 167 | 168 | // decompress the result 169 | let decompressed = Lzss::decode(&self.buffer[..])?; 170 | 171 | trace!("Payload AFTER decompress (len={}):\n{:?}", decompressed.len(), decompressed.hex_dump()); 172 | 173 | // check that the expected length matches (despite there also being a separate length 174 | // in the lzss header) 175 | if decompressed.len() != expected_length 176 | { 177 | return Err(anyhow::anyhow!("Decompressed data length mismatch from fragment transfer")); 178 | } 179 | 180 | // reassign the buffer with our new output 181 | self.buffer = decompressed; 182 | 183 | Ok(()) 184 | } 185 | 186 | // get the final payload once the transfer is complete 187 | pub fn unwrap_payload(self) -> Vec 188 | { 189 | assert_eq!(self.num_fragments, self.num_fragments_ack); 190 | 191 | return self.buffer; 192 | } 193 | } 194 | 195 | impl SubChannel { 196 | // create a new SubChannel 197 | pub fn new() -> Self { 198 | Self { 199 | file: None, 200 | compressed: None, 201 | is_replay: false, 202 | payload_size: 0, 203 | transfer: None, 204 | in_reliable_state: false, 205 | } 206 | } 207 | 208 | // read information about a file fragment 209 | fn read_file_info(&mut self, reader: &mut BitReader) -> anyhow::Result<()> 210 | where T: std::io::Read 211 | { 212 | // check if it's a file 213 | let is_file = reader.read_bit()?; 214 | 215 | trace!("is_file: {}", is_file); 216 | 217 | if is_file { 218 | let transfer_id = reader.read::(32)?; 219 | let filename = reader.read_string()?; 220 | 221 | trace!("[FILE] filename: {}", &filename); 222 | // this is file fragments 223 | self.file = Some(FileFragments{ 224 | // read the transfer id for the file 225 | transfer_id, 226 | 227 | // read the filename 228 | filename, 229 | }); 230 | 231 | // read if it's a replay demo 232 | let is_replay = reader.read_bit()?; 233 | if is_replay { 234 | self.is_replay = true; 235 | } 236 | 237 | trace!("[FILE] transfer_id: {}, is_replay: {}", transfer_id, is_replay); 238 | } 239 | 240 | Ok(()) 241 | } 242 | 243 | // read compression header info 244 | fn read_compress_info(&mut self, reader: &mut BitReader) -> anyhow::Result<()> 245 | where T: std::io::Read 246 | { 247 | // is it a compressed single block? 248 | let compressed = reader.read_bit()?; 249 | 250 | trace!("compressed: {}", compressed); 251 | 252 | if compressed { 253 | let uncompressed_size = reader.read::(26)? as usize; 254 | 255 | // mark it as compressed and read its uncompressed size 256 | self.compressed = Some(CompressedFragments { 257 | uncompressed_size 258 | }); 259 | 260 | trace!("uncompressed_size: {}", uncompressed_size); 261 | } 262 | 263 | Ok(()) 264 | } 265 | 266 | // called when a full payload has been received and needs to be processed before returning 267 | fn complete_transfer(&mut self) -> anyhow::Result 268 | { 269 | if let Some(data) = &self.compressed { 270 | trace!("Fragments were LZSS compressed, decompressing... (uncompressed_size={})", data.uncompressed_size); 271 | 272 | // if this is a compressed payload, decompress it here 273 | self.transfer.as_mut().unwrap().decompress_payload(data.uncompressed_size)?; 274 | 275 | trace!("Fragments successfully decompressed"); 276 | } 277 | 278 | let transfer_out = self.transfer.take().unwrap(); 279 | 280 | // return the completed transfer 281 | return Ok(transfer_out); 282 | } 283 | // read all of the SubChannel data for this SubChannel from the network 284 | // when the transfer is complete, returns Some(TransferBuffer) which contains the completed payload 285 | pub fn read_subchannel_data(&mut self, reader: &mut BitReader) -> anyhow::Result> 286 | where T: std::io::Read 287 | { 288 | trace!("Begin read_subchannel_data"); 289 | 290 | // position in the overall fragment buffer we're writing to 291 | let mut start_frag: usize = 0; 292 | 293 | // number of fragments contained in this packet 294 | let mut num_frags: usize = 0; 295 | 296 | // is it a single chunk of data? 297 | let single = !(reader.read_bit()?); 298 | 299 | // is it not a single block of data? if so, we need to start buffering the payload 300 | if !single 301 | { 302 | // the current offset fragment 303 | start_frag = reader.read::(18)? as usize; 304 | 305 | // the number of fragments in this packet 306 | num_frags = reader.read::(3)? as usize; 307 | 308 | trace!("Multi-fragment receive: (start_frag={}, num_frags={})", start_frag, num_frags); 309 | } 310 | 311 | // are we reading from the first packet? 312 | if start_frag == 0 { 313 | // if it is a single block 314 | if single { 315 | trace!("Starting new transfer (single block)"); 316 | 317 | // read if it's compressed 318 | self.read_compress_info(reader)?; 319 | 320 | // read the total amount of data being sent 321 | self.payload_size = reader.read::(18)? as usize; 322 | } else { 323 | trace!("Starting new transfer (multi-block)"); 324 | 325 | // read if it's file fragments 326 | self.read_file_info(reader)?; 327 | 328 | // read if it's compressed 329 | self.read_compress_info(reader)?; 330 | 331 | // read the total amount of data being sent 332 | self.payload_size = reader.read::(26)? as usize; 333 | } 334 | 335 | trace!("payload_size: {}", self.payload_size); 336 | 337 | // check for a file that's too large 338 | if self.payload_size > MAX_FILE_SIZE 339 | { 340 | return Err(anyhow::anyhow!("Exceeded max file transfer size!")); 341 | } 342 | 343 | // check for an invalid fragment 344 | if self.payload_size == 0 { 345 | return Err(anyhow::anyhow!("Invalid 0 length subfragment received!")); 346 | } 347 | 348 | // check for too large compressed file 349 | if let Some(x) = &self.compressed { 350 | if x.uncompressed_size > MAX_FILE_SIZE { 351 | return Err(anyhow::anyhow!("Exceeded max compressed file transfer size!")); 352 | } 353 | } 354 | 355 | // check for reinitialization, if so drop the old data 356 | if let Some(_x) = &self.transfer { 357 | warn!("Reinitializing transfer buffer due to fragment abort..."); 358 | } 359 | 360 | self.transfer = Some(TransferBuffer::new(self.payload_size)); 361 | } else { 362 | trace!("Continuing existing transfer..."); 363 | } 364 | 365 | let mut complete: bool = false; 366 | 367 | // check for no transfer, but we're somehow receiving transfer data 368 | if let None = &self.transfer { 369 | return Err(anyhow::anyhow!("Received fragment but no transfer pending")); 370 | }else if let Some(transfer) = &mut self.transfer { 371 | // read the actual bytes off the network 372 | complete = transfer.read_fragments(start_frag, num_frags, reader)?; 373 | 374 | // if we're still here, we received this chunk, flip the reliable state bit to ack 375 | // on the other end 376 | self.in_reliable_state = !self.in_reliable_state; 377 | } 378 | 379 | // has the full payload been received? if so, return the payload up. 380 | if complete { 381 | return Ok(Some(self.complete_transfer()?)); 382 | } 383 | 384 | Ok(None) 385 | } 386 | } -------------------------------------------------------------------------------- /src/steam/client.rs: -------------------------------------------------------------------------------- 1 | use steamworks::*; 2 | use std::time::Duration; 3 | use std::thread::JoinHandle; 4 | use std::sync::{Arc, Mutex, mpsc}; 5 | use std::net::{Ipv4Addr}; 6 | use anyhow::Context; 7 | use csgogcprotos::gcsystemmsgs::{EGCBaseClientMsg}; 8 | use csgogcprotos::cstrike15_gcmessages::{ECsgoGCMsg, CMsgGCCStrike15_v2_MatchmakingGC2ClientHello, CMsgGCCStrike15_v2_ClientRequestJoinServerData}; 9 | use crate::protoutil; 10 | 11 | /// Represents the state of a logged in steam client 12 | pub struct SteamClient 13 | { 14 | /// Multi-threaded interface 15 | _client: Client, 16 | 17 | /// Game coordinator packet queue 18 | gc_queue: GCMessageQueue, 19 | 20 | /// Thread object responsible for constantly calling Steam callbacks 21 | _main_thread: JoinHandle<()>, 22 | 23 | /// The current internal state of this client 24 | state: Arc>, 25 | } 26 | 27 | /// The current internal state of the steam client 28 | pub struct SteamClientState 29 | { 30 | /// CS:GO Matchmaking Account ID, received from matchmaking hello 31 | accountid: u32, 32 | } 33 | 34 | /// The result of a call to `request_join_server` 35 | #[derive(Debug)] 36 | pub struct JoinServerReservation 37 | { 38 | /// The steamid of the server 39 | pub serverid: u64, 40 | 41 | /// The IP address which Steam asks us to connect to 42 | pub direct_udp_ip: Ipv4Addr, 43 | 44 | /// Port which Steam asks us to connect to 45 | pub direct_udp_port: u32, 46 | 47 | /// The reservation ID, used in the C2S_CONNECT packet to verify 48 | /// a successful Steam request to join 49 | pub reservationid: u64, 50 | } 51 | 52 | /// Helper to transform an enum into a proto id 53 | fn proto_id(msg_type: u32) -> u32 54 | { 55 | return 0x80000000 | (msg_type); 56 | } 57 | 58 | impl SteamClient { 59 | /// Connect to Steam and the Game Coordinator 60 | /// Returns an active client 61 | pub fn connect() -> anyhow::Result 62 | { 63 | // create a steam client interface... the user must be logged in already on Steam 64 | let res = Client::init(); 65 | if let Err(e) = res { 66 | return Err(anyhow::anyhow!("Steam error: {}", e)) 67 | } 68 | 69 | let (client, single) = res.unwrap(); 70 | 71 | // create a gc packet connection 72 | let gc_queue = GCMessageQueue::new(client.clone()); 73 | 74 | // create a thread to constantly call steam callbacks 75 | let main_thread = SteamClient::spawn_main_thread(single, Duration::from_millis(10)); 76 | 77 | // internal state keeping that is updated when callbacks fire for certain packets 78 | let state = Arc::new(Mutex::new(SteamClientState{ 79 | accountid: 0xFFFFFFFF, 80 | })); 81 | 82 | // create steam client object 83 | let steam = SteamClient { 84 | _client: client, 85 | gc_queue, 86 | _main_thread: main_thread, 87 | state 88 | }; 89 | 90 | // perform a handshake to login to the GC 91 | steam.do_hello_handshake()?; 92 | 93 | Ok(steam) 94 | } 95 | 96 | 97 | /// Helper function which wraps a GC packet callback to automatically deserialize a protobuf message 98 | /// of a particular type before calling the supplied `callback` function. 99 | /// 100 | /// # Arguments 101 | /// 102 | /// * `enum_val` - The value of the packet type enum converted to a u32. The proto flag is automatically set. 103 | /// * `callback` - A callback function which accepts one argument, which is a protobuf::Message. This will be 104 | /// whatever type is specified by the `ProtoMsgType` type parameter. 105 | /// 106 | /// # Example 107 | /// ``` 108 | /// let _cb = self.proto_callback_wrapper:: 109 | /// ( 110 | /// ECsgoGCMsg::k_EMsgGCCStrike15_v2_MatchmakingGC2ClientHello as u32, 111 | /// move |pkt| { 112 | /// let account_id = pkt.get_account_id(); 113 | /// println!("Logged into CS:GO Matchmaking accountid='{}'", account_id); 114 | /// } 115 | /// ); 116 | ///``` 117 | fn proto_callback(&self, enum_val: u32, mut callback: CbProto) -> PktCallbackHandle 118 | where CbProto: FnMut(ProtoMsgType) + Send + 'static, 119 | ProtoMsgType: Send + protobuf::Message 120 | { 121 | self.gc_queue.install_callback( 122 | proto_id(enum_val), 123 | move |_pkt| { 124 | // decode protobuf packet 125 | let res = protoutil::deserialize::(&_pkt.body).unwrap(); 126 | callback(res); 127 | } 128 | ) 129 | } 130 | 131 | /// Helper function which performs a protobuf request to the game coordinator and waits on a response for a duration. 132 | /// When the response is received, calls `callback` with the decoded results of the packet. 133 | /// 134 | /// # Arguments 135 | /// 136 | /// * `to_send_type` - The packet enum value for the request being sent 137 | /// * `to_send` - The `protobuf::Message` structure for the packet being sent 138 | /// * `to_recv_type` - The packet enum value for the response packet 139 | /// * `timeout` - A timeout duration before the call fails and returns Err 140 | /// * `callback` - A callback which is executed in a separate thread when the response packet is received. 141 | /// The first argument is a protobuf type specified by `RecvMsgType` of message id `to_recv_type`. 142 | /// # Example 143 | /// ``` 144 | /// self.do_request::( 145 | /// ECsgoGCMsg::k_EMsgGCCStrike15_v2_ClientRequestJoinServerData as u32, 146 | /// msg, 147 | /// ECsgoGCMsg::k_EMsgGCCStrike15_v2_ClientRequestJoinServerData as u32, 148 | /// Duration::from_millis(1000), 149 | /// move |pkt| { 150 | /// println!("Received packet!"); 151 | /// } 152 | /// )?; 153 | /// ``` 154 | fn do_request( 155 | &self, 156 | to_send_type: u32, 157 | to_send: SendMsgType, 158 | to_recv_type: u32, 159 | timeout: Duration, 160 | mut callback: CbProto 161 | ) -> anyhow::Result<()> 162 | where CbProto: FnMut(RecvMsgType) + Send + 'static, 163 | SendMsgType: Send + protobuf::Message, 164 | RecvMsgType: Send + protobuf::Message 165 | { 166 | let (sender, receiver) = mpsc::sync_channel::(1); 167 | let sender_cl = sender.clone(); 168 | 169 | let _cb = self.proto_callback:: 170 | ( 171 | to_recv_type as u32, 172 | move |pkt| { 173 | callback(pkt); 174 | sender_cl.send(true).unwrap(); 175 | } 176 | ); 177 | 178 | // send request 179 | if !self.gc_queue.send_message( 180 | proto_id(to_send_type), 181 | &protoutil::serialize(to_send)?) { 182 | return Err(anyhow::anyhow!("Could not send message {}", to_send_type)) 183 | } 184 | 185 | // wait a bit for the response 186 | receiver 187 | .recv_timeout(timeout) 188 | .context("Timeout while waiting for message")?; 189 | 190 | return Ok(()) 191 | } 192 | 193 | /// Get an authentication ticket to authenticate with a server. 194 | /// 195 | /// This ticket must be sent to the server to verify that your user's identity 196 | /// and that you own the game. 197 | pub fn get_auth_ticket(&self) -> anyhow::Result> 198 | { 199 | let steam_user = self._client.user(); 200 | 201 | // discarding auth_handle because we don't plan to ever cancel this ticket 202 | let (_auth_handle, ticket) = steam_user.authentication_session_ticket(); 203 | 204 | return Ok(ticket) 205 | } 206 | 207 | /// Get the SteamID of the currently logged in user. 208 | pub fn get_steam_id(&self) -> steamworks::SteamId 209 | { 210 | return self._client.user().steam_id(); 211 | } 212 | 213 | /// Send a request to join a server and wait on the result 214 | /// Returns a `JoinServerReservation` struct which represents the server reservation 215 | pub fn request_join_server(&self, version: u32, serverid: u64, server_ip: u32, server_port: u32) -> anyhow::Result 216 | { 217 | let mut msg = CMsgGCCStrike15_v2_ClientRequestJoinServerData::new(); 218 | 219 | // matchmaking accountid, derived from steamid but held from matchamking hello 220 | msg.set_account_id(self.state.lock().unwrap().accountid); 221 | // version of the client connecting 222 | msg.set_version(version); 223 | // server's steamid 224 | msg.set_serverid(serverid); 225 | // server's ip (as we know it) 226 | msg.set_server_ip(server_ip); 227 | // server's port (as we know it) 228 | msg.set_server_port(server_port); 229 | 230 | // channel to wait on reservation when it comes in 231 | let (send, recv) = mpsc::sync_channel(1); 232 | 233 | // perform the request to join a server 234 | self.do_request::( 235 | ECsgoGCMsg::k_EMsgGCCStrike15_v2_ClientRequestJoinServerData as u32, 236 | msg, 237 | ECsgoGCMsg::k_EMsgGCCStrike15_v2_ClientRequestJoinServerData as u32, 238 | Duration::from_millis(10000), 239 | move |pkt| { 240 | // we got a reservation from the server 241 | let reservation = pkt.res.unwrap(); 242 | 243 | // interpret the protobuf packet into a structure we actually want to return 244 | let reservation = JoinServerReservation{ 245 | reservationid: reservation.get_reservationid(), 246 | direct_udp_ip: Ipv4Addr::from(reservation.get_direct_udp_ip()), 247 | direct_udp_port: reservation.get_direct_udp_port(), 248 | serverid: reservation.get_serverid() 249 | }; 250 | 251 | // send that over the channel, which will hit the recv.recv() and unblock it 252 | send.send(reservation).unwrap(); 253 | } 254 | )?; 255 | 256 | // wait until the request finishes or times out 257 | return Ok(recv.recv()?); 258 | } 259 | 260 | /// Send a client hello and block waiting for the response 261 | /// If successfully connected, returns Ok(). Otherwise, returns an error if the timeout was reached 262 | /// or there was an error sending. 263 | fn do_hello_handshake(&self) -> anyhow::Result<()> 264 | { 265 | let mut result : anyhow::Result = Ok(true); 266 | 267 | let (sender, receiver) = mpsc::sync_channel::(1); 268 | let sender_cl = sender.clone(); 269 | let state_cl = self.state.clone(); 270 | 271 | // prepare to receive the welcome message 272 | // cleans up callback after function exit 273 | let _cb = self.proto_callback:: 274 | ( 275 | ECsgoGCMsg::k_EMsgGCCStrike15_v2_MatchmakingGC2ClientHello as u32, 276 | move |pkt| { 277 | let account_id = pkt.get_account_id(); 278 | 279 | println!("Logged into CS:GO Matchmaking accountid='{}'", account_id); 280 | 281 | // remember our account id in the steam state 282 | state_cl.lock().unwrap().accountid = account_id; 283 | 284 | // alert that we've successfully logged in 285 | sender_cl.send(true).unwrap(); 286 | } 287 | ); 288 | 289 | // give it a few tries, since sometimes it takes steam a bit to warm up 290 | for _i in 0..10 291 | { 292 | // send a login request to the GC 293 | if !self.gc_queue.send_message( 294 | proto_id(EGCBaseClientMsg::k_EMsgGCClientHello as u32), 295 | &[]) { 296 | return Err(anyhow::anyhow!("Could not send GC hello")) 297 | } 298 | 299 | // wait a bit for the response 300 | result = receiver 301 | .recv_timeout(Duration::from_millis(10000)) 302 | .context("Timeout while waiting for GC welcome."); 303 | 304 | // did we get a welcome? okay we're good to go, don't retry again 305 | if let Ok(_) = result { 306 | return Ok(()) 307 | } 308 | } 309 | 310 | // we tried some times and failed, must be a true timeout 311 | Err(result.unwrap_err()) 312 | } 313 | 314 | /// Spawn the main callback handling thread 315 | fn spawn_main_thread(single: SingleClient, callback_interval: Duration) -> JoinHandle<()> { 316 | std::thread::spawn(move || { 317 | // loop constantly calling steam callbacks every 'frame' 318 | loop { 319 | single.run_callbacks(); 320 | ::std::thread::sleep(callback_interval); 321 | } 322 | }) 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/steam/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub use client::*; --------------------------------------------------------------------------------