/*
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 .
*/
#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)) {
LoginFailed();
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 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();
m_ws = nullptr;
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////
void WebSocket::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)
{
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;
}
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:
OnLoggedOn(buf, cbLen);
break;
}
}