1#include <base/logger.h>
2#include <base/os.h>
3#include <base/system.h>
4
5#include <engine/client.h>
6#include <engine/shared/demo.h>
7#include <engine/shared/network.h>
8#include <engine/shared/snapshot.h>
9#include <engine/storage.h>
10
11#include <game/gamecore.h>
12
13#include <memory>
14
15static const char *TOOL_NAME = "demo_extract_chat";
16
17class CClientSnapshotHandler
18{
19public:
20 struct CClientData
21 {
22 char m_aName[MAX_NAME_LENGTH];
23 };
24 CClientData m_aClients[MAX_CLIENTS];
25
26 char m_aaDemoSnapshotData[IClient::NUM_SNAPSHOT_TYPES][CSnapshot::MAX_SIZE];
27 CSnapshot *m_apAltSnapshots[IClient::NUM_SNAPSHOT_TYPES];
28
29 CClientSnapshotHandler() :
30 m_aClients()
31 {
32 mem_zero(block: m_aaDemoSnapshotData, size: sizeof(m_aaDemoSnapshotData));
33
34 for(int SnapshotType = 0; SnapshotType < IClient::NUM_SNAPSHOT_TYPES; SnapshotType++)
35 {
36 m_apAltSnapshots[SnapshotType] = (CSnapshot *)&m_aaDemoSnapshotData[SnapshotType];
37 }
38 }
39
40 int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo)
41 {
42 CUnpacker Unpacker;
43 CSnapshotBuilder Builder;
44 Builder.Init();
45 CNetObjHandler NetObjHandler;
46
47 int Num = pFrom->NumItems();
48 for(int Index = 0; Index < Num; Index++)
49 {
50 const CSnapshotItem *pFromItem = pFrom->GetItem(Index);
51 const int FromItemSize = pFrom->GetItemSize(Index);
52 const int ItemType = pFrom->GetItemType(Index);
53 const void *pData = pFromItem->Data();
54 Unpacker.Reset(pData, Size: FromItemSize);
55
56 void *pRawObj = NetObjHandler.SecureUnpackObj(Type: ItemType, pUnpacker: &Unpacker);
57 if(!pRawObj)
58 continue;
59
60 const int ItemSize = NetObjHandler.GetUnpackedObjSize(Type: ItemType);
61 void *pObj = Builder.NewItem(Type: pFromItem->Type(), Id: pFromItem->Id(), Size: ItemSize);
62 if(!pObj)
63 return -4;
64
65 mem_copy(dest: pObj, source: pRawObj, size: ItemSize);
66 }
67
68 return Builder.Finish(pSnapdata: pTo);
69 }
70
71 int SnapNumItems(int SnapId)
72 {
73 dbg_assert(SnapId >= 0 && SnapId < IClient::NUM_SNAPSHOT_TYPES, "Invalid SnapId: %d", SnapId);
74 return m_apAltSnapshots[SnapId]->NumItems();
75 }
76
77 IClient::CSnapItem SnapGetItem(int SnapId, int Index)
78 {
79 dbg_assert(SnapId >= 0 && SnapId < IClient::NUM_SNAPSHOT_TYPES, "Invalid SnapId: %d", SnapId);
80 const CSnapshot *pSnapshot = m_apAltSnapshots[SnapId];
81 const CSnapshotItem *pSnapshotItem = m_apAltSnapshots[SnapId]->GetItem(Index);
82 IClient::CSnapItem Item;
83 Item.m_Type = pSnapshot->GetItemType(Index);
84 Item.m_Id = pSnapshotItem->Id();
85 Item.m_pData = pSnapshotItem->Data();
86 Item.m_DataSize = pSnapshot->GetItemSize(Index);
87 return Item;
88 }
89
90 void OnNewSnapshot()
91 {
92 int Num = SnapNumItems(SnapId: IClient::SNAP_CURRENT);
93 for(int i = 0; i < Num; i++)
94 {
95 const IClient::CSnapItem Item = SnapGetItem(SnapId: IClient::SNAP_CURRENT, Index: i);
96
97 if(Item.m_Type == NETOBJTYPE_CLIENTINFO)
98 {
99 const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)Item.m_pData;
100 int ClientId = Item.m_Id;
101 if(ClientId < MAX_CLIENTS)
102 {
103 CClientData *pClient = &m_aClients[ClientId];
104 IntsToStr(pInts: pInfo->m_aName, NumInts: std::size(pInfo->m_aName), pStr: pClient->m_aName, StrSize: sizeof(pClient->m_aName));
105 }
106 }
107 }
108 }
109
110 void OnDemoPlayerSnapshot(void *pData, int Size)
111 {
112 unsigned char aAltSnapBuffer[CSnapshot::MAX_SIZE];
113 CSnapshot *pAltSnapBuffer = (CSnapshot *)aAltSnapBuffer;
114 const int AltSnapSize = UnpackAndValidateSnapshot(pFrom: (CSnapshot *)pData, pTo: pAltSnapBuffer);
115 if(AltSnapSize < 0)
116 return;
117
118 std::swap(a&: m_apAltSnapshots[IClient::SNAP_PREV], b&: m_apAltSnapshots[IClient::SNAP_CURRENT]);
119 mem_copy(dest: m_apAltSnapshots[IClient::SNAP_CURRENT], source: pAltSnapBuffer, size: AltSnapSize);
120
121 OnNewSnapshot();
122 }
123};
124
125class CDemoPlayerMessageListener : public CDemoPlayer::IListener
126{
127public:
128 CDemoPlayer *m_pDemoPlayer;
129 CClientSnapshotHandler *m_pClientSnapshotHandler;
130
131 void OnDemoPlayerSnapshot(void *pData, int Size) override
132 {
133 m_pClientSnapshotHandler->OnDemoPlayerSnapshot(pData, Size);
134 }
135
136 void OnDemoPlayerMessage(void *pData, int Size) override
137 {
138 CUnpacker Unpacker;
139 Unpacker.Reset(pData, Size);
140 CMsgPacker Packer(NETMSG_EX, true);
141
142 int Msg;
143 bool Sys;
144 CUuid Uuid;
145
146 int Result = UnpackMessageId(pId: &Msg, pSys: &Sys, pUuid: &Uuid, pUnpacker: &Unpacker, pPacker: &Packer);
147 if(Result == UNPACKMESSAGE_ERROR)
148 return;
149
150 if(!Sys)
151 {
152 CNetObjHandler NetObjHandler;
153 void *pRawMsg = NetObjHandler.SecureUnpackMsg(Type: Msg, pUnpacker: &Unpacker);
154 if(!pRawMsg)
155 return;
156
157 const IDemoPlayer::CInfo &Info = m_pDemoPlayer->Info()->m_Info;
158 char aTime[20];
159 str_time(centisecs: (int64_t)(Info.m_CurrentTick - Info.m_FirstTick) / SERVER_TICK_SPEED * 100, format: ETimeFormat::HOURS, buffer: aTime, buffer_size: sizeof(aTime));
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: *** %s\n", aTime, Prefix, pMsg->m_pMessage);
173 return;
174 }
175
176 if(pMsg->m_Team == TEAM_WHISPER_SEND)
177 printf(format: "[%s] %s: -> %s: %s\n", aTime, 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: %s\n", aTime, Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientId].m_aName, pMsg->m_pMessage);
180 else
181 printf(format: "[%s] %s: %s: %s\n", aTime, 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: "[%s] broadcast: %s\n", aTime, aBroadcast);
192 }
193 }
194 }
195 }
196 }
197};
198
199static int ExtractDemoChat(const char *pDemoFilePath, IStorage *pStorage)
200{
201 std::unique_ptr<CSnapshotDelta> pDemoSnapshotDelta = std::make_unique<CSnapshotDelta>();
202 CDemoPlayer DemoPlayer(pDemoSnapshotDelta.get(), 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 std::unique_ptr<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: pStorage.get());
253}
254