From 0c16f1942a2764943efc912378193bd16a1bb214 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Thu, 11 Aug 2022 20:50:51 -0400 Subject: [PATCH] Better appticket format. Thanks Nemirtingas. --- dll/appticket.h | 300 +++++++++++++++++++++++++++++++++++++++++++++++ dll/net.proto | 10 ++ dll/steam_user.h | 55 +++++++-- 3 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 dll/appticket.h diff --git a/dll/appticket.h b/dll/appticket.h new file mode 100644 index 0000000..94f92e7 --- /dev/null +++ b/dll/appticket.h @@ -0,0 +1,300 @@ + +struct AppTicketV1 +{ + // Total ticket size - 16 + uint32_t TicketSize; + uint32_t TicketVersion; + uint32_t Unk2; + std::vector UserData; + + void Reset() + { + TicketSize = 0; + TicketVersion = 0; + Unk2 = 0; + UserData.clear(); + } + + std::vector Serialize() + { + std::vector buffer; + uint8_t* pBuffer; + + buffer.resize(16 + UserData.size()); + pBuffer = buffer.data(); + *reinterpret_cast(pBuffer) = TicketSize; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketVersion; pBuffer += 4; + *reinterpret_cast(pBuffer) = UserData.size(); pBuffer += 4; + *reinterpret_cast(pBuffer) = Unk2; pBuffer += 4; + memcpy(pBuffer, UserData.data(), UserData.size()); + + return buffer; + } + + bool Deserialize(const uint8_t* pBuffer, size_t size) + { + if (size < 16) + return false; + + uint32_t user_data_size; + + TicketSize = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketVersion = *reinterpret_cast(pBuffer); pBuffer += 4; + user_data_size = *reinterpret_cast(pBuffer); pBuffer += 4; + + if (size < (user_data_size + 16)) + return false; + + Unk2 = *reinterpret_cast(pBuffer); pBuffer += 4; + UserData.resize(user_data_size); + memcpy(UserData.data(), pBuffer, user_data_size); + + return true; + } + + //inline uint32_t TicketSize() { return *reinterpret_cast(_Buffer); } + //inline uint32_t TicketVersion(){ return *reinterpret_cast(reinterpret_cast(_Buffer) + 4); } + //inline uint32_t UserDataSize() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 8); } + //inline uint32_t Unk2() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 12); } + //inline uint8_t* UserData() { return reinterpret_cast(reinterpret_cast(_Buffer) + 16); } +}; + +struct AppTicketV2 +{ + // Totals ticket size - 16 - TicketV1::UserData.size() + uint32_t TicketSize; + uint32_t TicketVersion; + uint64_t SteamID; + uint32_t AppID; + uint32_t Unk1; + uint32_t Unk2; + uint32_t TicketFlags; + uint32_t TicketIssueTime; + uint32_t TicketValidityEnd; + + static constexpr uint32_t LicenseBorrowed = 0x00000002; // Bit 1: IsLicenseBorrowed + static constexpr uint32_t LicenseTemporary = 0x00000004; // Bit 2: IsLicenseTemporary + + void Reset() + { + TicketSize = 0; + TicketVersion = 0; + SteamID = 0; + AppID = 0; + Unk1 = 0; + Unk2 = 0; + TicketFlags = 0; + TicketIssueTime = 0; + TicketValidityEnd = 0; + } + + std::vector Serialize() + { + std::vector buffer; + uint8_t* pBuffer; + + buffer.resize(40); + pBuffer = buffer.data(); + *reinterpret_cast(pBuffer) = TicketSize; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketVersion; pBuffer += 4; + *reinterpret_cast(pBuffer) = SteamID; pBuffer += 8; + *reinterpret_cast(pBuffer) = AppID; pBuffer += 4; + *reinterpret_cast(pBuffer) = Unk1; pBuffer += 4; + *reinterpret_cast(pBuffer) = Unk2; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketFlags; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketIssueTime; pBuffer += 4; + *reinterpret_cast(pBuffer) = TicketValidityEnd; + + return buffer; + } + + bool Deserialize(const uint8_t* pBuffer, size_t size) + { + if (size < 40) + return false; + + TicketSize = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketVersion = *reinterpret_cast(pBuffer); pBuffer += 4; + SteamID = *reinterpret_cast(pBuffer); pBuffer += 8; + AppID = *reinterpret_cast(pBuffer); pBuffer += 4; + Unk1 = *reinterpret_cast(pBuffer); pBuffer += 4; + Unk2 = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketFlags = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketIssueTime = *reinterpret_cast(pBuffer); pBuffer += 4; + TicketValidityEnd = *reinterpret_cast(pBuffer); + + return true; + } + + //inline uint32_t TicketSize() { return *reinterpret_cast(_Buffer); } + //inline uint32_t TicketVersion() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 4); } + //inline uint64_t SteamID() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 8); }; + //inline uint32_t AppID() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 16); } + //inline uint32_t Unk1() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 20); } + //inline uint32_t Unk2() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 24); } + //inline uint32_t TicketFlags() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 28); } + //inline uint32_t TicketIssueTime() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 32); } + //inline uint32_t TicketValidityEnd() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 36); } +}; + +struct AppTicketV4 +{ + std::vector AppIDs; + + bool HasVACStatus = false; + uint32_t VACStatus; + bool HasAppValue = false; + uint32_t AppValue; + + void Reset() + { + AppIDs.clear(); + HasVACStatus = false; + HasAppValue = false; + } + + std::vector Serialize() + { + std::vector appids = AppIDs; + if (appids.size() == 0) + { + appids.emplace_back(0); + } + + uint16_t appid_count = static_cast(appids.size() > 140 ? 140 : appids.size()); + size_t buffer_size = static_cast(appid_count) * 4ul + 2ul; + std::vector buffer; + uint8_t* pBuffer; + + if (HasAppValue) + {// VACStatus + AppValue + buffer_size += 4; + if (!HasVACStatus) + { + HasVACStatus = true; + VACStatus = 0; + } + } + if (HasVACStatus) + {// VACStatus only + buffer_size += 4; + } + + buffer.resize(buffer_size); + pBuffer = buffer.data(); + *reinterpret_cast(pBuffer) = appid_count; + pBuffer += 2; + + for (int i = 0; i < appid_count && i < 140; ++i) + { + *reinterpret_cast(pBuffer) = appids[i]; + pBuffer += 4; + } + + if (HasVACStatus) + { + *reinterpret_cast(pBuffer) = VACStatus; + pBuffer += 4; + } + if (HasAppValue) + { + *reinterpret_cast(pBuffer) = AppValue; + } + + return buffer; + } + + bool Deserialize(const uint8_t* pBuffer, size_t size) + { + if (size < 2) + return false; + + uint16_t appid_count = *reinterpret_cast(pBuffer); + if (size < (appid_count * 4 + 2) || appid_count >= 140) + return false; + + AppIDs.resize(appid_count); + pBuffer += 2; + size -= 2; + for (int i = 0; i < appid_count; ++i) + { + AppIDs[i] = *reinterpret_cast(pBuffer); + pBuffer += 4; + size -= 4; + } + + HasVACStatus = false; + HasAppValue = false; + + if (size < 4) + return true; + + HasVACStatus = true; + VACStatus = *reinterpret_cast(pBuffer); + pBuffer += 4; + size -= 4; + + if (size < 4) + return true; + + HasAppValue = true; + AppValue = *reinterpret_cast(pBuffer); + + return true; + } + + // Often 1 with empty appid + //inline uint16_t AppIDCount() { return *reinterpret_cast(_Buffer); } + //inline uint32_t* AppIDs() { return reinterpret_cast(reinterpret_cast(_Buffer) + 2); } + // Optional + //inline uint32_t VACStatus() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 2 + AppIDCount() * 4); } + // Optional + //inline uint32_t AppValue() { return *reinterpret_cast(reinterpret_cast(_Buffer) + 2 + AppIDCount() * 4 + 4); } +}; + +class DecryptedAppTicket +{ +public: + AppTicketV1 TicketV1; + AppTicketV2 TicketV2; + AppTicketV4 TicketV4; + + bool DeserializeTicket(const uint8_t* pBuffer, size_t buf_size) + { + if (!TicketV1.Deserialize(pBuffer, buf_size)) + return false; + + pBuffer += 16 + TicketV1.UserData.size(); + buf_size -= 16 + TicketV1.UserData.size(); + if (!TicketV2.Deserialize(pBuffer, buf_size)) + return false; + + if (TicketV2.TicketVersion > 2) + { + pBuffer += 40; + buf_size -= 40; + if (!TicketV4.Deserialize(pBuffer, buf_size)) + return false; + } + + return true; + } + + std::vector SerializeTicket() + { + std::vector buffer; + + TicketV1.TicketSize = TicketV1.UserData.size() + 40 + 2 + ((TicketV4.AppIDs.size() == 0 ? 1 : TicketV4.AppIDs.size()) * 4) + (TicketV4.HasVACStatus ? 4 : 0) + (TicketV4.HasAppValue ? 4 : 0); + TicketV2.TicketSize = TicketV1.TicketSize - TicketV1.UserData.size(); + + buffer = TicketV1.Serialize(); + + auto v = TicketV2.Serialize(); + + buffer.insert(buffer.end(), v.begin(), v.end()); + v = TicketV4.Serialize(); + buffer.insert(buffer.end(), v.begin(), v.end()); + + return buffer; + } +}; \ No newline at end of file diff --git a/dll/net.proto b/dll/net.proto index 2b1c2e6..32e7852 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -235,3 +235,13 @@ message Common_Message { uint32 source_ip = 128; uint32 source_port = 129; } + +//Non networking related protobufs + +message SteamAppTicket_pb { + uint32 ticket_version_no = 1; + uint32 crc_encryptedticket = 2; + uint32 cb_encrypteduserdata = 3; + uint32 cb_encrypted_appownershipticket = 4; + bytes encrypted_ticket = 5; +} diff --git a/dll/steam_user.h b/dll/steam_user.h index 8de6137..6186ef5 100644 --- a/dll/steam_user.h +++ b/dll/steam_user.h @@ -17,6 +17,8 @@ #include "base.h" +#include "appticket.h" + class Steam_User : public ISteamUser009, public ISteamUser010, @@ -367,7 +369,44 @@ SteamAPICall_t RequestEncryptedAppTicket( void *pDataToInclude, int cbDataToIncl EncryptedAppTicketResponse_t data; data.m_eResult = k_EResultOK; - encrypted_app_ticket = std::string((char *)pDataToInclude, cbDataToInclude); + DecryptedAppTicket ticket; + ticket.TicketV1.Reset(); + ticket.TicketV2.Reset(); + ticket.TicketV4.Reset(); + + ticket.TicketV1.TicketVersion = 1; + if (pDataToInclude) { + ticket.TicketV1.UserData.assign((uint8_t*)pDataToInclude, (uint8_t*)pDataToInclude + cbDataToInclude); + } + + ticket.TicketV2.TicketVersion = 4; + ticket.TicketV2.SteamID = settings->get_local_steam_id().ConvertToUint64(); + ticket.TicketV2.TicketIssueTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + ticket.TicketV2.TicketValidityEnd = ticket.TicketV2.TicketIssueTime + (21 * 24 * 60 * 60); + + for (int i = 0; i < 140; ++i) + { + AppId_t appid; + bool available; + std::string name; + if (!settings->getDLC(appid, appid, available, name)) break; + ticket.TicketV4.AppIDs.emplace_back(appid); + } + + ticket.TicketV4.HasVACStatus = true; + ticket.TicketV4.VACStatus = 0; + + auto serialized = ticket.SerializeTicket(); + + SteamAppTicket_pb pb; + pb.set_ticket_version_no(1); + pb.set_crc_encryptedticket(0); // TODO: Find out how to compute the CRC + pb.set_cb_encrypteduserdata(cbDataToInclude); + pb.set_cb_encrypted_appownershipticket(serialized.size() - 16); + pb.mutable_encrypted_ticket()->assign(serialized.begin(), serialized.end()); // TODO: Find how to encrypt datas + + encrypted_app_ticket = pb.SerializeAsString(); + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); } @@ -375,21 +414,17 @@ SteamAPICall_t RequestEncryptedAppTicket( void *pDataToInclude, int cbDataToIncl bool GetEncryptedAppTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket ) { PRINT_DEBUG("Steam_User::GetEncryptedAppTicket %i\n", cbMaxTicket); - if (!pcbTicket) return false; - unsigned int ticket_size = encrypted_app_ticket.size() + 126; + unsigned int ticket_size = encrypted_app_ticket.size(); if (!cbMaxTicket) { + if (!pcbTicket) return false; *pcbTicket = ticket_size; return true; } if (!pTicket) return false; - - //TODO figure out exact sizes? - if (ticket_size < cbMaxTicket) cbMaxTicket = ticket_size; - char ticket_base[] = {0x08, 0x01}; - memset(pTicket, 'g', cbMaxTicket); - memcpy(pTicket, ticket_base, sizeof(ticket_base)); - *pcbTicket = cbMaxTicket; + if (ticket_size > cbMaxTicket) return false; + encrypted_app_ticket.copy((char *)pTicket, cbMaxTicket); + if (pcbTicket) *pcbTicket = ticket_size; return true; }