summaryrefslogtreecommitdiff
path: root/protocols/Steam/src/steam_ws.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Steam/src/steam_ws.cpp')
-rw-r--r--protocols/Steam/src/steam_ws.cpp237
1 files changed, 237 insertions, 0 deletions
diff --git a/protocols/Steam/src/steam_ws.cpp b/protocols/Steam/src/steam_ws.cpp
new file mode 100644
index 0000000000..e6ee7f7d7a
--- /dev/null
+++ b/protocols/Steam/src/steam_ws.cpp
@@ -0,0 +1,237 @@
+/*
+Copyright (C) 2012-24 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+void __cdecl CSteamProto::ServerThread(void *)
+{
+ // load web socket servers first if needed
+ int iTimeDiff = db_get_dw(0, STEAM_MODULE, DBKEY_HOSTS_DATE);
+ int iHostCount = db_get_dw(0, STEAM_MODULE, DBKEY_HOSTS_COUNT);
+ if (!iHostCount || time(0) - iTimeDiff > 3600 * 24 * 7) { // once a week
+ if (!SendRequest(new GetHostsRequest(), &CSteamProto::OnGotHosts)) {
+ Logout();
+ return;
+ }
+ }
+
+ srand(time(0));
+ m_ws = nullptr;
+
+ CMStringA szHost;
+ do {
+ szHost.Format("Host%d", rand() % iHostCount);
+ szHost = db_get_sm(0, STEAM_MODULE, szHost);
+ szHost.Insert(0, "wss://");
+ szHost += "/cmsocket/";
+ }
+ while (ServerThreadStub(szHost));
+}
+
+bool CSteamProto::ServerThreadStub(const char *szHost)
+{
+ WebSocket<CSteamProto> ws(this);
+
+ NLHR_PTR pReply(ws.connect(m_hNetlibUser, szHost));
+ if (pReply == nullptr) {
+ debugLogA("websocket connection failed");
+ return false;
+ }
+
+ if (pReply->resultCode != 101) {
+ debugLogA("websocket connection failed: %d", pReply->resultCode);
+ return false;
+ }
+
+ m_ws = &ws;
+
+ debugLogA("Websocket connection succeeded");
+
+ // Send init packets
+ Login();
+
+ ws.run();
+
+ Logout();
+ m_impl.m_heartBeat.Stop();
+ m_ws = nullptr;
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WebSocket<CSteamProto>::process(const uint8_t *buf, size_t cbLen)
+{
+ uint32_t dwSign = *(uint32_t *)buf;
+ EMsg msgType = (EMsg)(dwSign & ~STEAM_PROTOCOL_MASK);
+
+ // now process the body
+ if (msgType == EMsg::Multi) {
+ buf += 8; cbLen -= 8;
+ p->ProcessMulti(buf, cbLen);
+ }
+ else p->ProcessMessage(buf, cbLen);
+}
+
+void CSteamProto::ProcessMulti(const uint8_t *buf, size_t cbLen)
+{
+ proto::MsgMulti pMulti(buf, cbLen);
+ if (pMulti == nullptr) {
+ debugLogA("Unable to decode multi message, exiting");
+ return;
+ }
+
+ debugLogA("processing %s multi message of size %d", (pMulti->size_unzipped) ? "zipped" : "normal", pMulti->message_body.len);
+
+ ptrA tmp;
+ if (pMulti->size_unzipped) {
+ tmp = (char *)mir_alloc(pMulti->size_unzipped + 1);
+ cbLen = FreeImage_ZLibGUnzip((uint8_t*)tmp.get(), pMulti->size_unzipped, pMulti->message_body.data, (unsigned)pMulti->message_body.len);
+ if (!cbLen) {
+ debugLogA("Unable to unzip multi message, exiting");
+ return;
+ }
+
+ buf = (const uint8_t *)tmp.get();
+ }
+ else {
+ buf = pMulti->message_body.data;
+ cbLen = pMulti->message_body.len;
+ }
+
+ while ((int)cbLen > 0) {
+ uint32_t cbPacketLen = *(uint32_t *)buf; buf += sizeof(uint32_t); cbLen -= sizeof(uint32_t);
+ ProcessMessage(buf, cbPacketLen);
+ buf += cbPacketLen; cbLen -= cbPacketLen;
+ }
+}
+
+void CSteamProto::ProcessMessage(const uint8_t *buf, size_t cbLen)
+{
+ Netlib_Dump(HNETLIBCONN(m_ws->getConn()), buf, cbLen, false, 0);
+
+ uint32_t dwSign = *(uint32_t *)buf; buf += sizeof(uint32_t); cbLen -= sizeof(uint32_t);
+ EMsg msgType = (EMsg)(dwSign & ~STEAM_PROTOCOL_MASK);
+ bool bIsProto = (dwSign & STEAM_PROTOCOL_MASK) != 0;
+
+ CMsgProtoBufHeader hdr;
+
+ if (msgType == EMsg::ChannelEncryptRequest || msgType == EMsg::ChannelEncryptResult) {
+ hdr.has_jobid_source = hdr.has_jobid_target = true;
+ hdr.jobid_source = *(int64_t *)buf; buf += sizeof(int64_t);
+ hdr.jobid_target = *(int64_t *)buf; buf += sizeof(int64_t);
+ }
+ else if (bIsProto) {
+ uint32_t hdrLen = *(uint32_t *)buf; buf += sizeof(uint32_t); cbLen -= sizeof(uint32_t);
+ proto::MsgProtoBufHeader tmpHeader(buf, hdrLen);
+ if (tmpHeader == nullptr) {
+ debugLogA("Unable to decode message header, exiting");
+ return;
+ }
+
+ memcpy(&hdr, tmpHeader, sizeof(hdr));
+ buf += hdrLen; cbLen -= hdrLen;
+
+ if (hdr.has_client_sessionid)
+ m_iSessionId = hdr.client_sessionid;
+ }
+ else {
+ debugLogA("Got unknown header, exiting");
+ return;
+ }
+
+ MsgCallback pCallback = 0;
+ {
+ mir_cslock lck(m_csRequests);
+ if (auto *pReq = m_arRequests.find((ProtoRequest *)&hdr.jobid_target)) {
+ pCallback = pReq->pCallback;
+ m_arRequests.remove(pReq);
+ }
+ }
+
+ if (pCallback) {
+ (this->*pCallback)(buf, cbLen);
+ return;
+ }
+
+ // persistent callbacks
+ switch (msgType) {
+ case EMsg::ClientLogOnResponse:
+ OnClientLogon(buf, cbLen);
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CSteamProto::WSSend(EMsg msgType, const ProtobufCppMessage &msg)
+{
+ CMsgProtoBufHeader hdr;
+ hdr.has_client_sessionid = hdr.has_steamid = hdr.has_jobid_source = hdr.has_jobid_target = true;
+ hdr.steamid = m_iSteamId;
+ hdr.client_sessionid = m_iSessionId;
+
+ switch (msgType) {
+ case EMsg::ClientHello:
+ hdr.jobid_source = -1;
+ break;
+
+ default:
+ hdr.jobid_source = getRandomInt();
+ break;
+ }
+
+ hdr.jobid_target = -1;
+
+ WSSendHeader(msgType, hdr, msg);
+}
+
+void CSteamProto::WSSendHeader(EMsg msgType, const CMsgProtoBufHeader &hdr, const ProtobufCppMessage &msg)
+{
+ uint32_t hdrLen = (uint32_t)protobuf_c_message_get_packed_size(&hdr);
+ MBinBuffer hdrbuf(hdrLen);
+ protobuf_c_message_pack(&hdr, (uint8_t *)hdrbuf.data());
+ hdrbuf.appendBefore(&hdrLen, sizeof(hdrLen));
+
+ uint32_t type = (uint32_t)msgType;
+ type |= STEAM_PROTOCOL_MASK;
+ hdrbuf.appendBefore(&type, sizeof(type));
+
+ MBinBuffer body(protobuf_c_message_get_packed_size(&msg));
+ protobuf_c_message_pack(&msg, body.data());
+
+ hdrbuf.append(body);
+ Netlib_Dump(HNETLIBCONN(m_ws->getConn()), hdrbuf.data(), hdrbuf.length(), true, 0);
+ m_ws->sendBinary(hdrbuf.data(), hdrbuf.length());
+}
+
+void CSteamProto::WSSendService(const char *pszServiceName, const ProtobufCppMessage &msg, MsgCallback pCallback)
+{
+ CMsgProtoBufHeader hdr;
+ hdr.has_client_sessionid = hdr.has_steamid = hdr.has_jobid_source = hdr.has_jobid_target = true;
+ hdr.jobid_source = getRandomInt();
+ hdr.jobid_target = -1;
+ hdr.target_job_name = (char *)pszServiceName;
+ hdr.realm = 1; hdr.has_realm = true;
+
+ if (pCallback) {
+ mir_cslock lck(m_csRequests);
+ m_arRequests.insert(new ProtoRequest(hdr.jobid_source, pCallback));
+ }
+
+ WSSendHeader(EMsg::ServiceMethodCallFromClientNonAuthed, hdr, msg);
+}