Skip to content

Commit 829c5eb

Browse files
committed
feat: half-done ghost voicechat
1 parent c64e708 commit 829c5eb

File tree

7 files changed

+191
-30
lines changed

7 files changed

+191
-30
lines changed

src/Features/Demo/NetworkGhostPlayer.cpp

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
#include "NetworkGhostPlayer.hpp"
22

33
#include "DemoGhostPlayer.hpp"
4-
#include "GhostLeaderboard.hpp"
54
#include "Event.hpp"
6-
#include "Scheduler.hpp"
75
#include "Features/Hud/Toasts.hpp"
86
#include "Features/NetMessage.hpp"
97
#include "Features/Session.hpp"
108
#include "Features/Speedrun/SpeedrunTimer.hpp"
119
#include "GhostEntity.hpp"
10+
#include "GhostLeaderboard.hpp"
1211
#include "Modules/Client.hpp"
1312
#include "Modules/Console.hpp"
1413
#include "Modules/Engine.hpp"
14+
#include "Modules/Scheme.hpp"
1515
#include "Modules/Server.hpp"
16+
#include "Modules/SteamAPI.hpp"
1617
#include "Modules/Surface.hpp"
17-
#include "Modules/Scheme.hpp"
18+
#include "Scheduler.hpp"
1819

1920
#include <chrono>
2021
#include <functional>
@@ -532,20 +533,54 @@ void NetworkManager::RunNetwork() {
532533
}
533534

534535
void NetworkManager::SendPlayerData() {
535-
sf::Packet packet;
536-
packet << HEADER::UPDATE << this->ID;
537-
auto player = client->GetPlayer(GET_SLOT() + 1);
538-
if (player) {
539-
bool grounded = player->ground_entity();
540-
packet << DataGhost{client->GetAbsOrigin(player), engine->GetAngles(engine->IsOrange() ? 0 : GET_SLOT()), client->GetViewOffset(player).z, grounded};
541-
} else {
542-
packet << DataGhost::Invalid();
536+
{
537+
sf::Packet packet;
538+
packet << HEADER::UPDATE << this->ID;
539+
auto player = client->GetPlayer(GET_SLOT() + 1);
540+
if (player) {
541+
bool grounded = player->ground_entity();
542+
packet << DataGhost{client->GetAbsOrigin(player), engine->GetAngles(engine->IsOrange() ? 0 : GET_SLOT()), client->GetViewOffset(player).z, grounded};
543+
} else {
544+
packet << DataGhost::Invalid();
545+
}
546+
547+
if (!ghost_TCP_only.GetBool()) {
548+
this->udpSocket.send(packet, this->serverIP, this->serverPort);
549+
} else {
550+
this->tcpSocket.send(packet);
551+
}
543552
}
544553

545-
if (!ghost_TCP_only.GetBool()) {
546-
this->udpSocket.send(packet, this->serverIP, this->serverPort);
547-
} else {
548-
this->tcpSocket.send(packet);
554+
// voice.
555+
{
556+
// read local microphone input.
557+
uint32_t nBytesAvailable = 0;
558+
EVoiceResult res = steam->SteamUser()->GetAvailableVoice(&nBytesAvailable, NULL, 0);
559+
560+
if (res == k_EVoiceResultOK && nBytesAvailable > 0) {
561+
uint32_t nBytesWritten = 0;
562+
MsgVoiceChatData_t msg;
563+
564+
// don't send more than 1 KB at a time.
565+
uint8_t buffer[1024 + sizeof(msg)];
566+
567+
res = steam->SteamUser()->GetVoice(true, buffer + sizeof(msg), 1024, &nBytesWritten, false, NULL, 0, NULL, 0);
568+
569+
if (res == k_EVoiceResultOK && nBytesWritten > 0) {
570+
msg.SetDataLength(nBytesWritten);
571+
memcpy(buffer, &msg, sizeof(msg));
572+
573+
sf::Packet packet;
574+
packet << HEADER::VOICE << this->ID;
575+
packet.append(buffer, sizeof(msg) + nBytesWritten);
576+
577+
if (!ghost_TCP_only.GetBool()) {
578+
this->udpSocket.send(packet, this->serverIP, this->serverPort);
579+
} else {
580+
this->tcpSocket.send(packet);
581+
}
582+
}
583+
}
549584
}
550585
}
551586

@@ -705,6 +740,9 @@ void NetworkManager::ReceiveUDPUpdates(std::vector<sf::Packet> &buffer) {
705740
} while (status == sf::Socket::Done);
706741
}
707742

743+
uint8_t g_pbUncompressedVoice[11025 * 2];
744+
uint32_t g_numUncompressedBytes;
745+
708746
void NetworkManager::Treat(sf::Packet &packet, bool udp) {
709747
HEADER header;
710748
sf::Uint32 ID;
@@ -999,6 +1037,27 @@ void NetworkManager::Treat(sf::Packet &packet, bool udp) {
9991037
}
10001038
break;
10011039
}
1040+
case HEADER::VOICE: {
1041+
console->Warning("voice packet.\n");
1042+
1043+
const auto pMessage = (uintptr_t)(packet.getData()) + 5;
1044+
1045+
const MsgVoiceChatData_t *pMsgVoiceData = (const MsgVoiceChatData_t *)pMessage;
1046+
1047+
uint8_t pbUncompressedVoice[11025 * 2];
1048+
uint32_t numUncompressedBytes = 0;
1049+
const uint8_t *pVoiceData = (const uint8_t *)pMessage;
1050+
pVoiceData += sizeof(MsgVoiceChatData_t);
1051+
1052+
EVoiceResult res = steam->SteamUser()->DecompressVoice(pVoiceData, pMsgVoiceData->GetDataLength(), pbUncompressedVoice, sizeof(pbUncompressedVoice), &numUncompressedBytes, 11025);
1053+
1054+
if (res == k_EVoiceResultOK && numUncompressedBytes > 0) {
1055+
memcpy(g_pbUncompressedVoice, pbUncompressedVoice, sizeof(pbUncompressedVoice));
1056+
g_numUncompressedBytes = numUncompressedBytes;
1057+
}
1058+
1059+
break;
1060+
}
10021061
default:
10031062
break;
10041063
}
@@ -1322,6 +1381,9 @@ ON_EVENT(FRAME) {
13221381
}
13231382
}
13241383

1384+
Command ghost_voice_on("+ghost_voice", +[](const CCommand &args) { steam->SteamUser()->StartVoiceRecording(); }, "+ghost_voice - push to talk in voice chat\n");
1385+
Command ghost_voice_off("-ghost_voice", +[](const CCommand &args) { steam->SteamUser()->StopVoiceRecording(); }, "-ghost_voice - push to talk in voice chat\n");
1386+
13251387
CON_COMMAND(ghost_chat, "ghost_chat - open the chat HUD for messaging other players\n") {
13261388
if (g_chatType == 0 && networkManager.isConnected) {
13271389
g_wasChatType = 0;

src/Features/Demo/NetworkGhostPlayer.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ enum class HEADER {
3030
COLOR_CHANGE,
3131
TAUNT,
3232
LOCATOR,
33+
VOICE,
3334
};
3435

3536
class NetworkManager {
@@ -128,3 +129,5 @@ extern Command ghost_name;
128129

129130
extern int g_chatType;
130131
extern int g_wasChatType;
132+
extern uint8_t g_pbUncompressedVoice[11025 * 2];
133+
extern uint32_t g_numUncompressedBytes;

src/Modules/Engine.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,21 @@ int __stdcall BinkWait_Detour(void *bink) {
934934
}
935935
}
936936

937+
938+
int Voice_GetOutputData_Detour(const int iChannel, char *copyBufBytes, const int copyBufSize, const int samplePosition, const int sampleCount);
939+
int (*Voice_GetOutputData)(const int iChannel, char *copyBufBytes, const int copyBufSize, const int samplePosition, const int sampleCount);
940+
static Hook Voice_GetOutputData_Hook(&Voice_GetOutputData_Detour);
941+
int Voice_GetOutputData_Detour(const int iChannel, char *copyBufBytes, const int copyBufSize, const int samplePosition, const int sampleCount) {
942+
memcpy(copyBufBytes, g_pbUncompressedVoice, g_numUncompressedBytes);
943+
944+
return g_numUncompressedBytes;
945+
946+
// _Voice_GetOutputData_Hook.Disable();
947+
// _Voice_GetOutputData();
948+
// _Voice_GetOutputData_Hook.Enable();
949+
}
950+
951+
937952
Color Engine::GetLightAtPoint(Vector point) {
938953
#ifdef _WIN32
939954
// MSVC bug workaround - COM interfaces apparently don't quite follow
@@ -1199,6 +1214,9 @@ bool Engine::Init() {
11991214
this->DestroyDebugMesh = this->g_physCollision->Original<_DestroyDebugMesh>(Offsets::DestroyDebugMesh);
12001215
}
12011216

1217+
Voice_GetOutputData = Memory::Scan<decltype(Voice_GetOutputData)>(this->Name(), "55 8B EC 51 8B 45 10 53 56");
1218+
Voice_GetOutputData_Hook.SetFunc(Voice_GetOutputData);
1219+
12021220
#ifdef _WIN32
12031221
auto bink_mod = Memory::GetModuleHandleByName(MODULE("binkw32"));
12041222
#else

src/Modules/SteamAPI.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ bool SteamAPI::Init() {
3838

3939
g_timeline->SetTimelineGameMode(k_ETimelineGameMode_Menus);
4040

41+
SteamUser = Memory::GetSymbolAddress<ISteamUser *(*)()>(steam_api, "SteamAPI_SteamUser_v021");
42+
4143
return this->hasLoaded = this->g_timeline;
4244
}
4345
void SteamAPI::Shutdown() {

src/Modules/SteamAPI.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class SteamAPI : public Module {
77
public:
88
ISteamTimeline *g_timeline = nullptr;
99

10+
ISteamUser *(*SteamUser)();
11+
1012
public:
1113
bool Init() override;
1214
void Shutdown() override;

src/SAR.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ bool SAR::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerF
3232
console = new Console();
3333
if (!console->Init())
3434
return false;
35-
35+
36+
HANDLE handle = OpenMutexA(MUTEX_ALL_ACCESS, false, "hl2_singleton_mutex");
37+
if (handle == NULL)
38+
return false;
39+
40+
if (ReleaseMutex(handle))
41+
console->Warning("Released mutex.\n");
42+
3643
modules = new Modules();
3744
features = new Features();
3845
cheats = new Cheats();

src/Utils/SDK/Other.hpp

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,24 @@ class CPlayerState {
8585
};
8686

8787
typedef struct player_info_s {
88-
// network xuid
8988
uint64_t xuid;
90-
// scoreboard information
89+
9190
char name[32];
92-
// local server user ID, unique while server is running
91+
9392
int userID;
94-
// global unique player identifer
93+
9594
char guid[32 + 1];
96-
// friends identification number
95+
9796
uint32_t friendsID;
98-
// friends name
97+
9998
char friendsName[32];
100-
// true, if player is a bot controlled by game.dll
99+
101100
bool fakeplayer;
102-
// true if player is the HLTV proxy
101+
103102
bool ishltv;
104-
// custom files CRC for this player
103+
105104
CRC32_t customFiles[4];
106-
// this counter increases each time the server downloaded a new file
105+
107106
unsigned char filesDownloaded;
108107
} player_info_t;
109108

@@ -128,10 +127,10 @@ struct PortalPlayerStatistics_t {
128127
};
129128

130129
struct PortalLeaderboardItem_t {
131-
uint64_t m_xuid; // 0x0000
132-
char m_szName[32]; // 0x0008
133-
char pad_0028[16]; // 0x0028
134-
int32_t m_iScore; // 0x0038
130+
uint64_t m_xuid;
131+
char m_szName[32];
132+
char pad_0028[16];
133+
int32_t m_iScore;
135134
};
136135

137136
enum ETimelineGameMode {
@@ -157,3 +156,71 @@ class ISteamTimeline {
157156
virtual void AddTimelineEvent(const char *pchIcon, const char *pchTitle, const char *pchDescription, uint32_t unPriority, float flStartOffsetSeconds, float flDurationSeconds, ETimelineEventClipPriority ePossibleClip) = 0;
158157
virtual void SetTimelineGameMode(ETimelineGameMode eMode) = 0;
159158
};
159+
160+
enum EVoiceResult {
161+
k_EVoiceResultOK = 0,
162+
k_EVoiceResultNotInitialized = 1,
163+
k_EVoiceResultNotRecording = 2,
164+
k_EVoiceResultNoData = 3,
165+
k_EVoiceResultBufferTooSmall = 4,
166+
k_EVoiceResultDataCorrupted = 5,
167+
k_EVoiceResultRestricted = 6,
168+
k_EVoiceResultUnsupportedCodec = 7,
169+
k_EVoiceResultReceiverOutOfDate = 8,
170+
k_EVoiceResultReceiverDidNotAnswer = 9,
171+
172+
};
173+
174+
enum EMessage {
175+
k_EMsgServerBegin = 0,
176+
k_EMsgServerSendInfo = k_EMsgServerBegin + 1,
177+
k_EMsgServerFailAuthentication = k_EMsgServerBegin + 2,
178+
k_EMsgServerPassAuthentication = k_EMsgServerBegin + 3,
179+
k_EMsgServerUpdateWorld = k_EMsgServerBegin + 4,
180+
k_EMsgServerExiting = k_EMsgServerBegin + 5,
181+
k_EMsgServerPingResponse = k_EMsgServerBegin + 6,
182+
k_EMsgServerPlayerHitSun = k_EMsgServerBegin + 7,
183+
k_EMsgClientBegin = 500,
184+
k_EMsgClientBeginAuthentication = k_EMsgClientBegin + 2,
185+
k_EMsgClientSendLocalUpdate = k_EMsgClientBegin + 3,
186+
k_EMsgP2PBegin = 600,
187+
k_EMsgP2PSendingTicket = k_EMsgP2PBegin + 1,
188+
k_EMsgVoiceChatBegin = 700,
189+
k_EMsgVoiceChatData = k_EMsgVoiceChatBegin + 2,
190+
k_EForceDWORD = 0x7fffffff,
191+
};
192+
193+
struct MsgVoiceChatData_t {
194+
MsgVoiceChatData_t()
195+
: m_dwMessageType((k_EMsgVoiceChatData)) {}
196+
unsigned long GetMessageType() const { return (m_dwMessageType); }
197+
198+
void SetDataLength(uint32_t unLength) { m_uDataLength = (unLength); }
199+
uint32_t GetDataLength() const { return (m_uDataLength); }
200+
201+
void SetSteamID(uint64_t steamID) { from_steamID = steamID; }
202+
uint64_t GetSteamID() const { return from_steamID; }
203+
204+
private:
205+
const unsigned long m_dwMessageType;
206+
uint32_t m_uDataLength;
207+
uint64_t from_steamID;
208+
};
209+
210+
class ISteamUser {
211+
public:
212+
virtual void *GetHSteamUser() = 0;
213+
virtual bool BLoggedOn() = 0;
214+
virtual uint64_t GetSteamID() = 0;
215+
virtual int InitiateGameConnection_DEPRECATED(void *pAuthBlob, int cbMaxAuthBlob, uint64_t steamIDGameServer, uint32_t unIPServer, uint16_t usPortServer, bool bSecure) = 0;
216+
virtual void TerminateGameConnection_DEPRECATED(uint32_t unIPServer, uint16_t usPortServer) = 0;
217+
virtual void TrackAppUsageEvent(uint64_t gameID, int eAppUsageEvent, const char *pchExtraInfo = "") = 0;
218+
virtual bool GetUserDataFolder(char *pchBuffer, int cubBuffer) = 0;
219+
virtual void StartVoiceRecording() = 0;
220+
virtual void StopVoiceRecording() = 0;
221+
virtual EVoiceResult GetAvailableVoice(uint32_t *pcbCompressed, uint32_t *pcbUncompressed_Deprecated = 0, uint32_t nUncompressedVoiceDesiredSampleRate_Deprecated = 0) = 0;
222+
virtual EVoiceResult GetVoice(bool bWantCompressed, void *pDestBuffer, uint32_t cbDestBufferSize, uint32_t *nBytesWritten, bool bWantUncompressed_Deprecated = false, void *pUncompressedDestBuffer_Deprecated = 0, uint32_t cbUncompressedDestBufferSize_Deprecated = 0, uint32_t *nUncompressBytesWritten_Deprecated = 0, uint32_t nUncompressedVoiceDesiredSampleRate_Deprecated = 0) = 0;
223+
virtual EVoiceResult DecompressVoice(const void *pCompressed, uint32_t cbCompressed, void *pDestBuffer, uint32_t cbDestBufferSize, uint32_t *nBytesWritten, uint32_t nDesiredSampleRate) = 0;
224+
virtual uint32_t GetVoiceOptimalSampleRate() = 0;
225+
// don't need the rest.
226+
};

0 commit comments

Comments
 (0)