Skip to main content

steamkit/base/gc/
client_msg_gc.rs

1//! Game coordinator messages: protobuf-backed and struct-backed.
2
3use crate::base::gc::msg_base_gc::IClientGCMsg;
4use crate::base::gc::packet_base_gc::IPacketGCMsg;
5use crate::base::generated::steam_language_internal::{
6    IGCSerializableHeader, IGCSerializableMessage, MsgGCHdr, MsgGCHdrProtoBuf, SteamSerializable,
7    SteamSerializableDeserialize,
8};
9use crate::base::msg_base::{MsgBase, Reader};
10use crate::error::SteamError;
11use crate::types::job_id::JobID;
12use steamkit_protos::gc::CMsgProtoBufHeader;
13
14/// A protobuf-backed game coordinator message.
15pub struct ClientGCMsgProtobuf<TBody>
16where
17    TBody: prost::Message + Default,
18{
19    /// The header of this message.
20    pub header: MsgGCHdrProtoBuf,
21    /// The body of this message.
22    pub body: TBody,
23    /// The raw payload that follows the body.
24    pub payload: MsgBase,
25}
26
27impl<TBody> ClientGCMsgProtobuf<TBody>
28where
29    TBody: prost::Message + Default,
30{
31    /// Creates a new message of the given GC message type.
32    pub fn new(e_msg: u32) -> Self {
33        Self::with_payload_reserve(e_msg, 0)
34    }
35
36    /// Like [`new`](Self::new), but reserves `payload_reserve` bytes for the payload.
37    pub fn with_payload_reserve(e_msg: u32, payload_reserve: usize) -> Self {
38        let mut header = MsgGCHdrProtoBuf::default();
39        header.set_gc_emsg(e_msg);
40        ClientGCMsgProtobuf {
41            header,
42            body: TBody::default(),
43            payload: MsgBase::new(payload_reserve),
44        }
45    }
46
47    /// Creates a reply to `reply_to`, targeting its source job.
48    pub fn reply(e_msg: u32, reply_to: &dyn IClientGCMsg) -> Self {
49        let mut msg = Self::new(e_msg);
50        msg.set_target_job_id(reply_to.source_job_id());
51        msg
52    }
53
54    /// Parses a received GC packet.
55    pub fn from_packet(msg: &dyn IPacketGCMsg) -> Result<Self, SteamError> {
56        if !msg.is_proto() {
57            return Err(SteamError::InvalidArgument(
58                "ClientGCMsgProtobuf used for non-proto message!".to_string(),
59            ));
60        }
61        let mut result = Self::new(msg.msg_type());
62        result.deserialize(msg.get_data())?;
63        Ok(result)
64    }
65
66    /// The body structure of this message.
67    pub fn body(&self) -> &TBody {
68        &self.body
69    }
70
71    /// The mutable body structure of this message.
72    pub fn body_mut(&mut self) -> &mut TBody {
73        &mut self.body
74    }
75
76    /// The header of this message.
77    pub fn header(&self) -> &MsgGCHdrProtoBuf {
78        &self.header
79    }
80
81    /// The mutable header of this message.
82    pub fn header_mut(&mut self) -> &mut MsgGCHdrProtoBuf {
83        &mut self.header
84    }
85
86    /// Shorthand for the protobuf header.
87    pub fn proto_header(&self) -> &CMsgProtoBufHeader {
88        &self.header.proto
89    }
90
91    /// Mutable shorthand for the protobuf header.
92    pub fn proto_header_mut(&mut self) -> &mut CMsgProtoBufHeader {
93        &mut self.header.proto
94    }
95
96    /// The payload buffer of this message.
97    pub fn payload(&self) -> &MsgBase {
98        &self.payload
99    }
100
101    /// The mutable payload buffer of this message.
102    pub fn payload_mut(&mut self) -> &mut MsgBase {
103        &mut self.payload
104    }
105}
106
107impl<TBody> IClientGCMsg for ClientGCMsgProtobuf<TBody>
108where
109    TBody: prost::Message + Default,
110{
111    fn is_proto(&self) -> bool {
112        true
113    }
114
115    fn msg_type(&self) -> u32 {
116        self.header.msg
117    }
118
119    fn target_job_id(&self) -> JobID {
120        JobID::from_value(self.header.proto.job_id_target.unwrap_or(u64::MAX))
121    }
122
123    fn set_target_job_id(&mut self, id: JobID) {
124        self.header.proto.job_id_target = Some(id.value());
125    }
126
127    fn source_job_id(&self) -> JobID {
128        JobID::from_value(self.header.proto.job_id_source.unwrap_or(u64::MAX))
129    }
130
131    fn set_source_job_id(&mut self, id: JobID) {
132        self.header.proto.job_id_source = Some(id.value());
133    }
134
135    fn serialize(&self) -> Vec<u8> {
136        let mut out = Vec::new();
137        self.header.write(&mut out);
138        out.extend_from_slice(&self.body.encode_to_vec());
139        out.extend_from_slice(&self.payload.payload);
140        out
141    }
142
143    fn deserialize(&mut self, data: &[u8]) -> Result<(), SteamError> {
144        let mut reader = Reader::new(data);
145        let header = MsgGCHdrProtoBuf::read(&mut reader)?;
146
147        let remaining = reader.remaining();
148        let body_bytes = reader.read_up_to(remaining);
149        let body = TBody::decode(body_bytes.as_slice())?;
150
151        let mut payload = MsgBase::new(0);
152        if !body_bytes.is_empty() {
153            payload.write_bytes(&body_bytes);
154            payload.seek(0);
155        }
156
157        self.header = header;
158        self.body = body;
159        self.payload = payload;
160        Ok(())
161    }
162}
163
164/// A struct-backed game coordinator message.
165pub struct ClientGCMsg<TBody>
166where
167    TBody: SteamSerializableDeserialize + IGCSerializableMessage,
168{
169    /// The header of this message.
170    pub header: MsgGCHdr,
171    /// The body of this message.
172    pub body: TBody,
173    /// The raw payload that follows the body.
174    pub payload: MsgBase,
175    /// The GC message type, taken from the body type.
176    pub msg_type: u32,
177}
178
179impl<TBody> ClientGCMsg<TBody>
180where
181    TBody: SteamSerializableDeserialize + IGCSerializableMessage,
182{
183    /// Creates a new message. The message type comes from the body type.
184    pub fn new(payload_reserve: usize) -> Self {
185        let body = TBody::default();
186        let msg_type = body.get_gc_emsg();
187        ClientGCMsg {
188            header: MsgGCHdr::default(),
189            body,
190            payload: MsgBase::new(payload_reserve),
191            msg_type,
192        }
193    }
194
195    /// Creates a reply to `reply_to`, targeting its source job.
196    pub fn reply(reply_to: &dyn IClientGCMsg) -> Self {
197        let mut msg = Self::new(0);
198        msg.set_target_job_id(reply_to.source_job_id());
199        msg
200    }
201
202    /// Parses a received GC packet.
203    pub fn from_packet(msg: &dyn IPacketGCMsg) -> Result<Self, SteamError> {
204        if msg.is_proto() {
205            return Err(SteamError::InvalidArgument(
206                "ClientGCMsg used for proto message!".to_string(),
207            ));
208        }
209        let mut result = Self::new(0);
210        result.deserialize(msg.get_data())?;
211        Ok(result)
212    }
213
214    /// The body structure of this message.
215    pub fn body(&self) -> &TBody {
216        &self.body
217    }
218
219    /// The mutable body structure of this message.
220    pub fn body_mut(&mut self) -> &mut TBody {
221        &mut self.body
222    }
223
224    /// The header of this message.
225    pub fn header(&self) -> &MsgGCHdr {
226        &self.header
227    }
228
229    /// The mutable header of this message.
230    pub fn header_mut(&mut self) -> &mut MsgGCHdr {
231        &mut self.header
232    }
233
234    /// The payload buffer of this message.
235    pub fn payload(&self) -> &MsgBase {
236        &self.payload
237    }
238
239    /// The mutable payload buffer of this message.
240    pub fn payload_mut(&mut self) -> &mut MsgBase {
241        &mut self.payload
242    }
243}
244
245impl<TBody> IClientGCMsg for ClientGCMsg<TBody>
246where
247    TBody: SteamSerializableDeserialize + IGCSerializableMessage,
248{
249    fn is_proto(&self) -> bool {
250        false
251    }
252
253    fn msg_type(&self) -> u32 {
254        self.msg_type
255    }
256
257    fn target_job_id(&self) -> JobID {
258        JobID::from_value(self.header.target_job_id)
259    }
260
261    fn set_target_job_id(&mut self, id: JobID) {
262        self.header.target_job_id = id.value();
263    }
264
265    fn source_job_id(&self) -> JobID {
266        JobID::from_value(self.header.source_job_id)
267    }
268
269    fn set_source_job_id(&mut self, id: JobID) {
270        self.header.source_job_id = id.value();
271    }
272
273    fn serialize(&self) -> Vec<u8> {
274        let mut out = Vec::new();
275        self.header.write(&mut out);
276        self.body.write(&mut out);
277        out.extend_from_slice(&self.payload.payload);
278        out
279    }
280
281    fn deserialize(&mut self, data: &[u8]) -> Result<(), SteamError> {
282        let mut reader = Reader::new(data);
283        let header = MsgGCHdr::read(&mut reader)?;
284        let body = TBody::read(&mut reader)?;
285
286        let remaining = reader.remaining();
287        let rest = reader.read_up_to(remaining);
288        let mut payload = MsgBase::new(0);
289        if !rest.is_empty() {
290            payload.write_bytes(&rest);
291            payload.seek(0);
292        }
293
294        self.header = header;
295        self.body = body;
296        self.payload = payload;
297        Ok(())
298    }
299}