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 | |
12 | static const char *TOOL_NAME = "demo_extract_chat" ; |
13 | |
14 | class CClientSnapshotHandler |
15 | { |
16 | public: |
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 | |
129 | class CDemoPlayerMessageListener : public CDemoPlayer::IListener |
130 | { |
131 | public: |
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 | |
199 | static int (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 | |
232 | int 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 | |