1#include <base/dbg.h>
2#include <base/logger.h>
3#include <base/mem.h>
4#include <base/os.h>
5#include <base/str.h>
6#include <base/time.h>
7
8#include <engine/client.h>
9#include <engine/shared/demo.h>
10#include <engine/shared/network.h>
11#include <engine/shared/snapshot.h>
12#include <engine/storage.h>
13
14#include <game/gamecore.h>
15
16#include <memory>
17
18static const char *TOOL_NAME = "demo_extract_chat";
19
20class CClientSnapshotHandler
21{
22public:
23 struct CClientData
24 {
25 char m_aName[MAX_NAME_LENGTH];
26 };
27 CClientData m_aClients[MAX_CLIENTS];
28
29 CSnapshotBuffer m_aDemoSnapshotData[IClient::NUM_SNAPSHOT_TYPES];
30 CSnapshot *m_apAltSnapshots[IClient::NUM_SNAPSHOT_TYPES];
31
32 CClientSnapshotHandler() :
33 m_aClients()
34 {
35 mem_zero(block: m_aDemoSnapshotData, size: sizeof(m_aDemoSnapshotData));
36
37 for(int SnapshotType = 0; SnapshotType < IClient::NUM_SNAPSHOT_TYPES; SnapshotType++)
38 {
39 m_apAltSnapshots[SnapshotType] = m_aDemoSnapshotData[SnapshotType].AsSnapshot();
40 }
41 }
42
43 int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshotBuffer *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
58 if(ItemType <= 0)
59 {
60 // Don't add extended item type descriptions, they get
61 // added implicitly (== 0).
62 //
63 // Don't add items of unknown item types either (< 0).
64 continue;
65 }
66
67 Unpacker.Reset(pData, Size: FromItemSize);
68
69 void *pSecuredData = NetObjHandler.SecureUnpackObj(Type: ItemType, pUnpacker: &Unpacker);
70 if(!pSecuredData)
71 continue;
72
73 const int ItemSize = NetObjHandler.GetUnpackedObjSize(Type: ItemType);
74 if(!Builder.NewItem(Type: ItemType, Id: pFromItem->Id(), pData: pSecuredData, Size: ItemSize))
75 {
76 return -4;
77 }
78 }
79
80 return Builder.Finish(pBuffer: pTo);
81 }
82
83 int SnapNumItems(int SnapId)
84 {
85 dbg_assert(SnapId >= 0 && SnapId < IClient::NUM_SNAPSHOT_TYPES, "Invalid SnapId: %d", SnapId);
86 return m_apAltSnapshots[SnapId]->NumItems();
87 }
88
89 IClient::CSnapItem SnapGetItem(int SnapId, int Index)
90 {
91 dbg_assert(SnapId >= 0 && SnapId < IClient::NUM_SNAPSHOT_TYPES, "Invalid SnapId: %d", SnapId);
92 const CSnapshot *pSnapshot = m_apAltSnapshots[SnapId];
93 const CSnapshotItem *pSnapshotItem = m_apAltSnapshots[SnapId]->GetItem(Index);
94 IClient::CSnapItem Item;
95 Item.m_Type = pSnapshot->GetItemType(Index);
96 Item.m_Id = pSnapshotItem->Id();
97 Item.m_pData = pSnapshotItem->Data();
98 Item.m_DataSize = pSnapshot->GetItemSize(Index);
99 return Item;
100 }
101
102 void OnNewSnapshot()
103 {
104 int Num = SnapNumItems(SnapId: IClient::SNAP_CURRENT);
105 for(int i = 0; i < Num; i++)
106 {
107 const IClient::CSnapItem Item = SnapGetItem(SnapId: IClient::SNAP_CURRENT, Index: i);
108
109 if(Item.m_Type == NETOBJTYPE_CLIENTINFO)
110 {
111 const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)Item.m_pData;
112 int ClientId = Item.m_Id;
113 if(ClientId < MAX_CLIENTS)
114 {
115 CClientData *pClient = &m_aClients[ClientId];
116 IntsToStr(pInts: pInfo->m_aName, NumInts: std::size(pInfo->m_aName), pStr: pClient->m_aName, StrSize: sizeof(pClient->m_aName));
117 }
118 }
119 }
120 }
121
122 void OnDemoPlayerSnapshot(void *pData, int Size)
123 {
124 CSnapshotBuffer AltSnapBuffer;
125 const int AltSnapSize = UnpackAndValidateSnapshot(pFrom: (CSnapshot *)pData, pTo: &AltSnapBuffer);
126 if(AltSnapSize < 0)
127 return;
128
129 std::swap(a&: m_apAltSnapshots[IClient::SNAP_PREV], b&: m_apAltSnapshots[IClient::SNAP_CURRENT]);
130 mem_copy(dest: m_apAltSnapshots[IClient::SNAP_CURRENT], source: AltSnapBuffer.AsSnapshot(), size: AltSnapSize);
131
132 OnNewSnapshot();
133 }
134};
135
136class CDemoPlayerMessageListener : public CDemoPlayer::IListener
137{
138public:
139 CDemoPlayer *m_pDemoPlayer;
140 CClientSnapshotHandler *m_pClientSnapshotHandler;
141
142 void OnDemoPlayerSnapshot(void *pData, int Size) override
143 {
144 m_pClientSnapshotHandler->OnDemoPlayerSnapshot(pData, Size);
145 }
146
147 void OnDemoPlayerMessage(void *pData, int Size) override
148 {
149 CUnpacker Unpacker;
150 Unpacker.Reset(pData, Size);
151 CMsgPacker Packer(NETMSG_EX, true);
152
153 int Msg;
154 bool Sys;
155 CUuid Uuid;
156
157 int Result = UnpackMessageId(pId: &Msg, pSys: &Sys, pUuid: &Uuid, pUnpacker: &Unpacker, pPacker: &Packer);
158 if(Result == UNPACKMESSAGE_ERROR)
159 return;
160
161 if(!Sys)
162 {
163 CNetObjHandler NetObjHandler;
164 void *pRawMsg = NetObjHandler.SecureUnpackMsg(Type: Msg, pUnpacker: &Unpacker);
165 if(!pRawMsg)
166 return;
167
168 const IDemoPlayer::CInfo &Info = m_pDemoPlayer->Info()->m_Info;
169 char aTime[20];
170 str_time(centisecs: (int64_t)(Info.m_CurrentTick - Info.m_FirstTick) / SERVER_TICK_SPEED * 100, format: ETimeFormat::HOURS, buffer: aTime, buffer_size: sizeof(aTime));
171
172 if(Msg == NETMSGTYPE_SV_CHAT)
173 {
174 CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
175
176 if(pMsg->m_ClientId > -1 && m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName[0] == '\0')
177 return;
178
179 const char *Prefix = pMsg->m_Team > 1 ? "whisper" : (pMsg->m_Team ? "teamchat" : "chat");
180
181 if(pMsg->m_ClientId < 0)
182 {
183 printf(format: "[%s] %s: *** %s\n", aTime, Prefix, pMsg->m_pMessage);
184 return;
185 }
186
187 if(pMsg->m_Team == TEAM_WHISPER_SEND)
188 printf(format: "[%s] %s: -> %s: %s\n", aTime, Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
189 else if(pMsg->m_Team == TEAM_WHISPER_RECV)
190 printf(format: "[%s] %s: <- %s: %s\n", aTime, Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
191 else
192 printf(format: "[%s] %s: %s: %s\n", aTime, Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
193 }
194 else if(Msg == NETMSGTYPE_SV_BROADCAST)
195 {
196 CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg;
197 char aBroadcast[1024];
198 while((pMsg->m_pMessage = str_next_token(str: pMsg->m_pMessage, delim: "\n", buffer: aBroadcast, buffer_size: sizeof(aBroadcast))))
199 {
200 if(aBroadcast[0] != '\0')
201 {
202 printf(format: "[%s] broadcast: %s\n", aTime, aBroadcast);
203 }
204 }
205 }
206 }
207 }
208};
209
210static int ExtractDemoChat(const char *pDemoFilePath, CSnapshotDelta *pSnapshotDelta, CSnapshotDelta *pSnapshotDeltaSixup, IStorage *pStorage)
211{
212 CDemoPlayer DemoPlayer(pSnapshotDelta, pSnapshotDeltaSixup, false);
213
214 if(DemoPlayer.Load(pStorage, pConsole: nullptr, pFilename: pDemoFilePath, StorageType: IStorage::TYPE_ALL_OR_ABSOLUTE) == -1)
215 {
216 log_error(TOOL_NAME, "Demo file '%s' failed to load: %s", pDemoFilePath, DemoPlayer.ErrorMessage());
217 return -1;
218 }
219
220 CClientSnapshotHandler Handler;
221 CDemoPlayerMessageListener Listener;
222 Listener.m_pDemoPlayer = &DemoPlayer;
223 Listener.m_pClientSnapshotHandler = &Handler;
224 DemoPlayer.SetListener(&Listener);
225
226 const CDemoPlayer::CPlaybackInfo *pInfo = DemoPlayer.Info();
227 CNetBase::Init();
228 DemoPlayer.Play();
229
230 while(DemoPlayer.IsPlaying())
231 {
232 DemoPlayer.Update(RealTime: false);
233 if(pInfo->m_Info.m_Paused)
234 break;
235 }
236
237 DemoPlayer.Stop();
238
239 return 0;
240}
241
242static std::unique_ptr<CSnapshotDelta> CreateSnapshotDelta()
243{
244 std::unique_ptr<CSnapshotDelta> pResult = std::make_unique<CSnapshotDelta>();
245 CNetObjHandler NetObjHandler;
246 for(int i = 0; i < NUM_NETOBJTYPES; i++)
247 {
248 pResult->SetStaticsize(ItemType: i, Size: NetObjHandler.GetObjSize(Type: i));
249 }
250 return pResult;
251}
252
253static std::unique_ptr<CSnapshotDelta> CreateSnapshotDeltaSixup()
254{
255 std::unique_ptr<CSnapshotDelta> pResult = std::make_unique<CSnapshotDelta>();
256 protocol7::CNetObjHandler NetObjHandler7;
257 // HACK: only set static size for items, which were available in the first 0.7 release
258 // so new items don't break the snapshot delta
259 static const int OLD_NUM_NETOBJTYPES = 23;
260 for(int i = 0; i < OLD_NUM_NETOBJTYPES; i++)
261 {
262 pResult->SetStaticsize(ItemType: i, Size: NetObjHandler7.GetObjSize(Type: i));
263 }
264 return pResult;
265}
266
267int main(int argc, const char *argv[])
268{
269 // Create storage before setting logger to avoid log messages from storage creation
270 std::unique_ptr<IStorage> pStorage = CreateLocalStorage();
271
272 CCmdlineFix CmdlineFix(&argc, &argv);
273 log_set_global_logger_default();
274
275 if(!pStorage)
276 {
277 log_error(TOOL_NAME, "Error creating local storage");
278 return -1;
279 }
280
281 if(argc != 2)
282 {
283 log_error(TOOL_NAME, "Usage: %s <demo_filename>", TOOL_NAME);
284 return -1;
285 }
286
287 std::unique_ptr<CSnapshotDelta> pSnapshotDelta = CreateSnapshotDelta();
288 std::unique_ptr<CSnapshotDelta> pSnapshotDeltaSixup = CreateSnapshotDeltaSixup();
289
290 return ExtractDemoChat(pDemoFilePath: argv[1], pSnapshotDelta: pSnapshotDelta.get(), pSnapshotDeltaSixup: pSnapshotDeltaSixup.get(), pStorage: pStorage.get());
291}
292