1#include <base/logger.h>
2#include <base/system.h>
3
4#include <engine/client.h>
5#include <engine/shared/demo.h>
6#include <engine/shared/network.h>
7#include <engine/shared/snapshot.h>
8#include <engine/storage.h>
9
10#include <game/gamecore.h>
11
12static const char *TOOL_NAME = "demo_extract_chat";
13
14class CClientSnapshotHandler
15{
16public:
17 struct CClientData
18 {
19 char m_aName[MAX_NAME_LENGTH];
20 };
21 CClientData m_aClients[MAX_CLIENTS];
22
23 CSnapshotStorage::CHolder m_aDemoSnapshotHolders[IClient::NUM_SNAPSHOT_TYPES];
24 char m_aaaDemoSnapshotData[IClient::NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE];
25 CSnapshotStorage::CHolder *m_apSnapshots[IClient::NUM_SNAPSHOT_TYPES];
26
27 CClientSnapshotHandler() :
28 m_aClients(), m_aDemoSnapshotHolders()
29 {
30 mem_zero(block: m_aaaDemoSnapshotData, size: sizeof(m_aaaDemoSnapshotData));
31
32 for(int SnapshotType = 0; SnapshotType < IClient::NUM_SNAPSHOT_TYPES; SnapshotType++)
33 {
34 m_apSnapshots[SnapshotType] = &m_aDemoSnapshotHolders[SnapshotType];
35 m_apSnapshots[SnapshotType]->m_pSnap = (CSnapshot *)&m_aaaDemoSnapshotData[SnapshotType][0];
36 m_apSnapshots[SnapshotType]->m_pAltSnap = (CSnapshot *)&m_aaaDemoSnapshotData[SnapshotType][1];
37 m_apSnapshots[SnapshotType]->m_SnapSize = 0;
38 m_apSnapshots[SnapshotType]->m_AltSnapSize = 0;
39 m_apSnapshots[SnapshotType]->m_Tick = -1;
40 }
41 }
42
43 int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo)
44 {
45 CUnpacker Unpacker;
46 CSnapshotBuilder Builder;
47 Builder.Init();
48 CNetObjHandler NetObjHandler;
49
50 int Num = pFrom->NumItems();
51 for(int Index = 0; Index < Num; Index++)
52 {
53 const CSnapshotItem *pFromItem = pFrom->GetItem(Index);
54 const int FromItemSize = pFrom->GetItemSize(Index);
55 const int ItemType = pFrom->GetItemType(Index);
56 const void *pData = pFromItem->Data();
57 Unpacker.Reset(pData, Size: FromItemSize);
58
59 void *pRawObj = NetObjHandler.SecureUnpackObj(Type: ItemType, pUnpacker: &Unpacker);
60 if(!pRawObj)
61 continue;
62
63 const int ItemSize = NetObjHandler.GetUnpackedObjSize(Type: ItemType);
64 void *pObj = Builder.NewItem(Type: pFromItem->Type(), Id: pFromItem->Id(), Size: ItemSize);
65 if(!pObj)
66 return -4;
67
68 mem_copy(dest: pObj, source: pRawObj, size: ItemSize);
69 }
70
71 return Builder.Finish(pSnapdata: pTo);
72 }
73
74 int SnapNumItems(int SnapId)
75 {
76 dbg_assert(SnapId >= 0 && SnapId < IClient::NUM_SNAPSHOT_TYPES, "invalid SnapId");
77 if(!m_apSnapshots[SnapId])
78 return 0;
79 return m_apSnapshots[SnapId]->m_pAltSnap->NumItems();
80 }
81
82 void *SnapGetItem(int SnapId, int Index, IClient::CSnapItem *pItem)
83 {
84 dbg_assert(SnapId >= 0 && SnapId < IClient::NUM_SNAPSHOT_TYPES, "invalid SnapId");
85 const CSnapshotItem *pSnapshotItem = m_apSnapshots[SnapId]->m_pAltSnap->GetItem(Index);
86 pItem->m_DataSize = m_apSnapshots[SnapId]->m_pAltSnap->GetItemSize(Index);
87 pItem->m_Type = m_apSnapshots[SnapId]->m_pAltSnap->GetItemType(Index);
88 pItem->m_Id = pSnapshotItem->Id();
89 return (void *)pSnapshotItem->Data();
90 }
91
92 void OnNewSnapshot()
93 {
94 int Num = SnapNumItems(SnapId: IClient::SNAP_CURRENT);
95 for(int i = 0; i < Num; i++)
96 {
97 IClient::CSnapItem Item;
98 const void *pData = SnapGetItem(SnapId: IClient::SNAP_CURRENT, Index: i, pItem: &Item);
99
100 if(Item.m_Type == NETOBJTYPE_CLIENTINFO)
101 {
102 const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)pData;
103 int ClientId = Item.m_Id;
104 if(ClientId < MAX_CLIENTS)
105 {
106 CClientData *pClient = &m_aClients[ClientId];
107 IntsToStr(pInts: &pInfo->m_Name0, NumInts: 4, pStr: pClient->m_aName, StrSize: sizeof(pClient->m_aName));
108 }
109 }
110 }
111 }
112
113 void OnDemoPlayerSnapshot(void *pData, int Size)
114 {
115 unsigned char aAltSnapBuffer[CSnapshot::MAX_SIZE];
116 CSnapshot *pAltSnapBuffer = (CSnapshot *)aAltSnapBuffer;
117 const int AltSnapSize = UnpackAndValidateSnapshot(pFrom: (CSnapshot *)pData, pTo: pAltSnapBuffer);
118 if(AltSnapSize < 0)
119 return;
120
121 std::swap(a&: m_apSnapshots[IClient::SNAP_PREV], b&: m_apSnapshots[IClient::SNAP_CURRENT]);
122 mem_copy(dest: m_apSnapshots[IClient::SNAP_CURRENT]->m_pSnap, source: pData, size: Size);
123 mem_copy(dest: m_apSnapshots[IClient::SNAP_CURRENT]->m_pAltSnap, source: pAltSnapBuffer, size: AltSnapSize);
124
125 OnNewSnapshot();
126 }
127};
128
129class CDemoPlayerMessageListener : public CDemoPlayer::IListener
130{
131public:
132 CDemoPlayer *m_pDemoPlayer;
133 CClientSnapshotHandler *m_pClientSnapshotHandler;
134
135 void OnDemoPlayerSnapshot(void *pData, int Size) override
136 {
137 m_pClientSnapshotHandler->OnDemoPlayerSnapshot(pData, Size);
138 }
139
140 void OnDemoPlayerMessage(void *pData, int Size) override
141 {
142 CUnpacker Unpacker;
143 Unpacker.Reset(pData, Size);
144 CMsgPacker Packer(NETMSG_EX, true);
145
146 int Msg;
147 bool Sys;
148 CUuid Uuid;
149
150 int Result = UnpackMessageId(pId: &Msg, pSys: &Sys, pUuid: &Uuid, pUnpacker: &Unpacker, pPacker: &Packer);
151 if(Result == UNPACKMESSAGE_ERROR)
152 return;
153
154 if(!Sys)
155 {
156 CNetObjHandler NetObjHandler;
157 void *pRawMsg = NetObjHandler.SecureUnpackMsg(Type: Msg, pUnpacker: &Unpacker);
158 if(!pRawMsg)
159 return;
160
161 if(Msg == NETMSGTYPE_SV_CHAT)
162 {
163 CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
164
165 if(pMsg->m_ClientId > -1 && m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName[0] == '\0')
166 return;
167
168 const char *Prefix = pMsg->m_Team > 1 ? "whisper" : (pMsg->m_Team ? "teamchat" : "chat");
169
170 if(pMsg->m_ClientId < 0)
171 {
172 printf(format: "%s: *** %s\n", Prefix, pMsg->m_pMessage);
173 return;
174 }
175
176 if(pMsg->m_Team == TEAM_WHISPER_SEND)
177 printf(format: "%s: -> %s: %s\n", Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
178 else if(pMsg->m_Team == TEAM_WHISPER_RECV)
179 printf(format: "%s: <- %s: %s\n", Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
180 else
181 printf(format: "%s: %s: %s\n", Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
182 }
183 else if(Msg == NETMSGTYPE_SV_BROADCAST)
184 {
185 CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg;
186 char aBroadcast[1024];
187 while((pMsg->m_pMessage = str_next_token(str: pMsg->m_pMessage, delim: "\n", buffer: aBroadcast, buffer_size: sizeof(aBroadcast))))
188 {
189 if(aBroadcast[0] != '\0')
190 {
191 printf(format: "broadcast: %s\n", aBroadcast);
192 }
193 }
194 }
195 }
196 }
197};
198
199static int ExtractDemoChat(const char *pDemoFilePath, IStorage *pStorage)
200{
201 CSnapshotDelta DemoSnapshotDelta;
202 CDemoPlayer DemoPlayer(&DemoSnapshotDelta, false);
203
204 if(DemoPlayer.Load(pStorage, pConsole: nullptr, pFilename: pDemoFilePath, StorageType: IStorage::TYPE_ALL_OR_ABSOLUTE) == -1)
205 {
206 log_error(TOOL_NAME, "Demo file '%s' failed to load: %s", pDemoFilePath, DemoPlayer.ErrorMessage());
207 return -1;
208 }
209
210 CClientSnapshotHandler Handler;
211 CDemoPlayerMessageListener Listener;
212 Listener.m_pDemoPlayer = &DemoPlayer;
213 Listener.m_pClientSnapshotHandler = &Handler;
214 DemoPlayer.SetListener(&Listener);
215
216 const CDemoPlayer::CPlaybackInfo *pInfo = DemoPlayer.Info();
217 CNetBase::Init();
218 DemoPlayer.Play();
219
220 while(DemoPlayer.IsPlaying())
221 {
222 DemoPlayer.Update(RealTime: false);
223 if(pInfo->m_Info.m_Paused)
224 break;
225 }
226
227 DemoPlayer.Stop();
228
229 return 0;
230}
231
232int main(int argc, const char *argv[])
233{
234 // Create storage before setting logger to avoid log messages from storage creation
235 IStorage *pStorage = CreateLocalStorage();
236
237 CCmdlineFix CmdlineFix(&argc, &argv);
238 log_set_global_logger_default();
239
240 if(!pStorage)
241 {
242 log_error(TOOL_NAME, "Error creating local storage");
243 return -1;
244 }
245
246 if(argc != 2)
247 {
248 log_error(TOOL_NAME, "Usage: %s <demo_filename>", TOOL_NAME);
249 return -1;
250 }
251
252 return ExtractDemoChat(pDemoFilePath: argv[1], pStorage);
253}
254