diff --git a/CMakeLists.txt b/CMakeLists.txt index 2aef79299..f28de9571 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2630,6 +2630,7 @@ if(TOOLS) config_retrieve.cpp config_store.cpp crapnet.cpp + demo_extract_chat.cpp dilate.cpp dummy_map.cpp map_convert_07.cpp @@ -2999,6 +3000,7 @@ if(TOOLS) set(TARGET_TOOLS config_retrieve config_store + demo_extract_chat dilate map_convert_07 map_diff diff --git a/src/tools/demo_extract_chat.cpp b/src/tools/demo_extract_chat.cpp new file mode 100644 index 000000000..729ddcd67 --- /dev/null +++ b/src/tools/demo_extract_chat.cpp @@ -0,0 +1,245 @@ +#include +#include +#include +#include + +static const char *TOOL_NAME = "demo_extract_chat"; + +class CClientSnapshotHandler +{ +public: + struct CClientData + { + char m_aName[MAX_NAME_LENGTH]; + }; + CClientData m_aClients[MAX_CLIENTS]; + + CSnapshotStorage::CHolder m_aDemoSnapshotHolders[IClient::NUM_SNAPSHOT_TYPES]; + char m_aaaDemoSnapshotData[IClient::NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; + CSnapshotStorage::CHolder *m_apSnapshots[IClient::NUM_SNAPSHOT_TYPES]; + + CClientSnapshotHandler() : + m_aClients(), m_aDemoSnapshotHolders() + { + mem_zero(m_aaaDemoSnapshotData, sizeof(m_aaaDemoSnapshotData)); + + for(int SnapshotType = 0; SnapshotType < IClient::NUM_SNAPSHOT_TYPES; SnapshotType++) + { + m_apSnapshots[SnapshotType] = &m_aDemoSnapshotHolders[SnapshotType]; + m_apSnapshots[SnapshotType]->m_pSnap = (CSnapshot *)&m_aaaDemoSnapshotData[SnapshotType][0]; + m_apSnapshots[SnapshotType]->m_pAltSnap = (CSnapshot *)&m_aaaDemoSnapshotData[SnapshotType][1]; + m_apSnapshots[SnapshotType]->m_SnapSize = 0; + m_apSnapshots[SnapshotType]->m_AltSnapSize = 0; + m_apSnapshots[SnapshotType]->m_Tick = -1; + } + } + + int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo) + { + CUnpacker Unpacker; + CSnapshotBuilder Builder; + Builder.Init(); + CNetObjHandler NetObjHandler; + + int Num = pFrom->NumItems(); + for(int Index = 0; Index < Num; Index++) + { + const CSnapshotItem *pFromItem = pFrom->GetItem(Index); + const int FromItemSize = pFrom->GetItemSize(Index); + const int ItemType = pFrom->GetItemType(Index); + const void *pData = pFromItem->Data(); + Unpacker.Reset(pData, FromItemSize); + + void *pRawObj = NetObjHandler.SecureUnpackObj(ItemType, &Unpacker); + if(!pRawObj) + continue; + + const int ItemSize = NetObjHandler.GetUnpackedObjSize(ItemType); + void *pObj = Builder.NewItem(pFromItem->Type(), pFromItem->ID(), ItemSize); + if(!pObj) + return -4; + + mem_copy(pObj, pRawObj, ItemSize); + } + + return Builder.Finish(pTo); + } + + int SnapNumItems(int SnapID) + { + dbg_assert(SnapID >= 0 && SnapID < IClient::NUM_SNAPSHOT_TYPES, "invalid SnapID"); + if(!m_apSnapshots[SnapID]) + return 0; + return m_apSnapshots[SnapID]->m_pAltSnap->NumItems(); + } + + void *SnapGetItem(int SnapID, int Index, IClient::CSnapItem *pItem) + { + dbg_assert(SnapID >= 0 && SnapID < IClient::NUM_SNAPSHOT_TYPES, "invalid SnapID"); + const CSnapshotItem *pSnapshotItem = m_apSnapshots[SnapID]->m_pAltSnap->GetItem(Index); + pItem->m_DataSize = m_apSnapshots[SnapID]->m_pAltSnap->GetItemSize(Index); + pItem->m_Type = m_apSnapshots[SnapID]->m_pAltSnap->GetItemType(Index); + pItem->m_ID = pSnapshotItem->ID(); + return (void *)pSnapshotItem->Data(); + } + + void OnNewSnapshot() + { + int Num = SnapNumItems(IClient::SNAP_CURRENT); + for(int i = 0; i < Num; i++) + { + IClient::CSnapItem Item; + const void *pData = SnapGetItem(IClient::SNAP_CURRENT, i, &Item); + + if(Item.m_Type == NETOBJTYPE_CLIENTINFO) + { + const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)pData; + int ClientID = Item.m_ID; + if(ClientID < MAX_CLIENTS) + { + CClientData *pClient = &m_aClients[ClientID]; + IntsToStr(&pInfo->m_Name0, 4, pClient->m_aName); + } + } + } + } + + void OnDemoPlayerSnapshot(void *pData, int Size) + { + unsigned char aAltSnapBuffer[CSnapshot::MAX_SIZE]; + CSnapshot *pAltSnapBuffer = (CSnapshot *)aAltSnapBuffer; + const int AltSnapSize = UnpackAndValidateSnapshot((CSnapshot *)pData, pAltSnapBuffer); + if(AltSnapSize < 0) + return; + + std::swap(m_apSnapshots[IClient::SNAP_PREV], m_apSnapshots[IClient::SNAP_CURRENT]); + mem_copy(m_apSnapshots[IClient::SNAP_CURRENT]->m_pSnap, pData, Size); + mem_copy(m_apSnapshots[IClient::SNAP_CURRENT]->m_pAltSnap, pAltSnapBuffer, AltSnapSize); + + OnNewSnapshot(); + } +}; + +class CDemoPlayerMessageListener : public CDemoPlayer::IListener +{ +public: + CDemoPlayer *m_pDemoPlayer; + CClientSnapshotHandler *m_pClientSnapshotHandler; + + void OnDemoPlayerSnapshot(void *pData, int Size) override + { + m_pClientSnapshotHandler->OnDemoPlayerSnapshot(pData, Size); + } + + void OnDemoPlayerMessage(void *pData, int Size) override + { + CUnpacker Unpacker; + Unpacker.Reset(pData, Size); + CMsgPacker Packer(NETMSG_EX, true); + + int Msg; + bool Sys; + CUuid Uuid; + + int Result = UnpackMessageID(&Msg, &Sys, &Uuid, &Unpacker, &Packer); + if(Result == UNPACKMESSAGE_ERROR) + return; + + if(!Sys) + { + CNetObjHandler NetObjHandler; + void *pRawMsg = NetObjHandler.SecureUnpackMsg(Msg, &Unpacker); + if(!pRawMsg) + return; + + if(Msg == NETMSGTYPE_SV_CHAT) + { + CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; + + if(pMsg->m_ClientID > -1 && m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientID].m_aName[0] == '\0') + return; + + const char *Prefix = pMsg->m_Team > 1 ? "whisper" : (pMsg->m_Team ? "teamchat" : "chat"); + + if(pMsg->m_ClientID < 0) + { + printf("%s: *** %s\n", Prefix, pMsg->m_pMessage); + return; + } + + if(pMsg->m_Team == 2) // WHISPER SEND + printf("%s: -> %s: %s\n", Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientID].m_aName, pMsg->m_pMessage); + else if(pMsg->m_Team == 3) // WHISPER RECEIVE + printf("%s: <- %s: %s\n", Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientID].m_aName, pMsg->m_pMessage); + else + printf("%s: %s: %s\n", Prefix, m_pClientSnapshotHandler->m_aClients[pMsg->m_ClientID].m_aName, pMsg->m_pMessage); + } + else if(Msg == NETMSGTYPE_SV_BROADCAST) + { + CNetMsg_Sv_Broadcast *pMsg = (CNetMsg_Sv_Broadcast *)pRawMsg; + char aBroadcast[1024]; + while((pMsg->m_pMessage = str_next_token(pMsg->m_pMessage, "\n", aBroadcast, sizeof(aBroadcast)))) + { + if(aBroadcast[0] != '\0') + { + printf("broadcast: %s\n", aBroadcast); + } + } + } + } + } +}; + +int Process(const char *pDemoFilePath, IStorage *pStorage) +{ + CSnapshotDelta DemoSnapshotDelta; + CDemoPlayer DemoPlayer(&DemoSnapshotDelta, false); + + if(DemoPlayer.Load(pStorage, nullptr, pDemoFilePath, IStorage::TYPE_ALL_OR_ABSOLUTE) == -1) + { + dbg_msg(TOOL_NAME, "Demo file '%s' failed to load: %s", pDemoFilePath, DemoPlayer.ErrorMessage()); + return -1; + } + + CClientSnapshotHandler Handler; + CDemoPlayerMessageListener Listener; + Listener.m_pDemoPlayer = &DemoPlayer; + Listener.m_pClientSnapshotHandler = &Handler; + DemoPlayer.SetListener(&Listener); + + const CDemoPlayer::CPlaybackInfo *pInfo = DemoPlayer.Info(); + CNetBase::Init(); + DemoPlayer.Play(); + + while(DemoPlayer.IsPlaying()) + { + DemoPlayer.Update(false); + if(pInfo->m_Info.m_Paused) + break; + } + + DemoPlayer.Stop(); + + return 0; +} + +int main(int argc, const char *argv[]) +{ + IStorage *pStorage = CreateLocalStorage(); + if(!pStorage) + { + dbg_msg(TOOL_NAME, "Error loading storage"); + return -1; + } + + CCmdlineFix CmdlineFix(&argc, &argv); + log_set_global_logger_default(); + + if(argc != 2) + { + dbg_msg(TOOL_NAME, "Usage: %s [demo_filename]", TOOL_NAME); + return -1; + } + + return Process(argv[1], pStorage); +}