bool C4Record::Rec(C4PacketType eCtrlType, C4ControlPacket *pCtrl, int iFrame) { if (!fRecording) return false; // create copy C4IDPacket Pkt = C4IDPacket(eCtrlType, pCtrl, false); if (!Pkt.getPkt()) return false; C4ControlPacket *pCtrlCpy = static_cast<C4ControlPacket *>(Pkt.getPkt()); // prepare for recording pCtrlCpy->PreRec(this); // record it return Rec(iFrame, DecompileToBuf<StdCompilerBinWrite>(Pkt), RCT_CtrlPkt); }
void C4PacketList::CompileFunc(StdCompiler *pComp) { // unpack packets if (pComp->isCompiler()) { // Read until no further sections available while (pComp->Name("IDPacket")) { // Read the packet C4IDPacket *pPkt = new C4IDPacket(); try { pComp->Value(*pPkt); pComp->NameEnd(); } catch (...) { delete pPkt; throw; } // Terminator? if (!pPkt->getPkt()) { delete pPkt; break; } // Add to list Add(pPkt); } pComp->NameEnd(); } else { // Write all packets for (C4IDPacket *pPkt = pFirst; pPkt; pPkt = pPkt->pNext) pComp->Value(mkNamingAdapt(*pPkt, "IDPacket")); // Terminate, if no naming is available if (!pComp->hasNaming()) { C4IDPacket Pkt; pComp->Value(mkNamingAdapt(Pkt, "IDPacket")); } } }
void C4Playback::Check(C4RecordChunkType eType, const uint8_t *pData, int iSize) { // only if enabled if (DoNoDebugRec > 0) return; if (Game.FrameCounter < DEBUGREC_START_FRAME) return; C4PktDebugRec PktInReplay; bool fHasPacketFromHead = false; #ifdef DEBUGREC_EXTFILE #ifdef DEBUGREC_EXTFILE_WRITE // writing of external debugrec file DbgRecFile.Write(&eType, sizeof eType); int32_t iSize32 = iSize; DbgRecFile.Write(&iSize32, sizeof iSize32); DbgRecFile.Write(pData, iSize); return; #else int32_t iSize32 = 0; C4RecordChunkType eTypeRec = RCT_Undefined; DbgRecFile.Read(&eTypeRec, sizeof eTypeRec); DbgRecFile.Read(&iSize32, sizeof iSize32); if (iSize32) { StdBuf buf; buf.SetSize(iSize32); DbgRecFile.Read(buf.getMData(), iSize32); PktInReplay = C4PktDebugRec(eTypeRec, buf); } #endif #else // check debug rec in list C4IDPacket *pkt; if (pkt = DebugRec.firstPkt()) { // copy from list PktInReplay = *static_cast<C4PktDebugRec *>(pkt->getPkt()); DebugRec.Delete(pkt); } else { // special sync check skip... while (currChunk != chunks.end() && currChunk->Type == RCT_CtrlPkt) { C4IDPacket Packet(*currChunk->pPkt); C4ControlPacket *pCtrlPck = static_cast<C4ControlPacket *>(Packet.getPkt()); assert(!pCtrlPck->Sync()); Game.Control.ExecControlPacket(Packet.getPktType(), pCtrlPck); NextChunk(); } // record end? if (currChunk == chunks.end() || currChunk->Type == RCT_End || Finished) { Log("DebugRec end: All in sync!"); ++DoNoDebugRec; return; } // unpack directly from head if (currChunk->Type != eType) { DebugRecError(FormatString("Playback type %x, this type %x", currChunk->Type, eType).getData()); return; } PktInReplay = *currChunk->pDbg; fHasPacketFromHead = true; } #endif // DEBUGREC_EXTFILE // record end? if (PktInReplay.getType() == RCT_End) { Log("DebugRec end: All in sync (2)!"); ++DoNoDebugRec; return; } // replay packet is unpacked to PktInReplay now; check it if (PktInReplay.getType() != eType) { DebugRecError(FormatString("Type %s != %s", GetRecordChunkTypeName(PktInReplay.getType()), GetRecordChunkTypeName(eType)).getData()); return; } if (PktInReplay.getSize() != iSize) { DebugRecError(FormatString("Size %d != %d", (int)PktInReplay.getSize(), (int)iSize).getData()); } // check packet data if (memcmp(PktInReplay.getData(), pData, iSize)) { StdStrBuf sErr; sErr.Format("DbgRecPkt Type %s, size %d", GetRecordChunkTypeName(eType), iSize); sErr.Append(" Replay: "); StdBuf replay(PktInReplay.getData(), PktInReplay.getSize()); sErr.Append(GetDbgRecPktData(eType, replay)); sErr.Append(" Here: "); StdBuf here(pData, iSize); sErr.Append(GetDbgRecPktData(eType, here)); DebugRecError(sErr.getData()); } // packet is fine, jump over it if (fHasPacketFromHead) NextChunk(); }
void C4Playback::Strip() { // Strip what? const bool fStripPlayers = false; const bool fStripSyncChecks = false; const bool fStripDebugRec = true; const bool fCheckCheat = false; const bool fStripMessages = true; // const bool fCheckEMControl = true; const int32_t iEndFrame = -1; // Iterate over chunk list for (chunks_t::iterator i = chunks.begin(); i != chunks.end();) { // Strip rest of record? if (iEndFrame >= 0 && i->Frame > iEndFrame) { // Remove this and all remaining chunks while (i != chunks.end()) { i->Delete(); i = chunks.erase(i); } // Push new End-Chunk C4RecordChunk EndChunk; EndChunk.Frame = iEndFrame; EndChunk.Type = RCT_End; chunks.push_back(EndChunk); // Done break; } switch (i->Type) { case RCT_Ctrl: { // Iterate over controls C4Control *pCtrl = i->pCtrl; for (C4IDPacket *pPkt = pCtrl->firstPkt(), *pNext; pPkt; pPkt = pNext) { pNext = pCtrl->nextPkt(pPkt); switch (pPkt->getPktType()) { // Player join: Strip player file (if possible) case CID_JoinPlr: if (fStripPlayers) { C4ControlJoinPlayer *pJoinPlr = static_cast<C4ControlJoinPlayer *>(pPkt->getPkt()); pJoinPlr->Strip(); } break; // EM commands: May be cheats, so log them case CID_Script: case CID_EMMoveObj: case CID_EMDrawTool: if (fCheckCheat) Log(DecompileToBuf<StdCompilerINIWrite>( mkNamingAdapt(*pPkt, FormatString("Frame %d", i->Frame) .getData())).getData()); break; // Strip sync check case CID_SyncCheck: if (fStripSyncChecks) { i->pCtrl->Remove(pPkt); } break; } } // Strip empty control lists (always) if (!pCtrl->firstPkt()) { i->Delete(); i = chunks.erase(i); } else i++; } break; case RCT_CtrlPkt: { bool fStripThis = false; switch (i->pPkt->getPktType()) { // EM commands: May be cheats, so log them case CID_Script: case CID_EMMoveObj: case CID_EMDrawTool: if (fCheckCheat) Log(DecompileToBuf<StdCompilerINIWrite>( mkNamingAdapt(*i->pPkt, FormatString("Frame %d", i->Frame) .getData())).getData()); break; // Strip some stuff case CID_SyncCheck: if (fStripSyncChecks) fStripThis = true; break; case CID_Message: if (fStripMessages) fStripThis = true; break; } if (fStripThis) { i->Delete(); i = chunks.erase(i); } else i++; } break; case RCT_End: i++; break; default: // Strip debugrec if (fStripDebugRec) { i->Delete(); i = chunks.erase(i); } else i++; } } }
bool C4Network2IO::HandlePacket(const C4NetIOPacket &rPacket, C4Network2IOConnection *pConn, bool fThread) { // security: add connection reference if (!pConn) return false; pConn->AddRef(); // accept only PID_Conn and PID_Ping on non-accepted connections if(!pConn->isHalfAccepted()) if(rPacket.getStatus() != PID_Conn && rPacket.getStatus() != PID_Ping && rPacket.getStatus() != PID_ConnRe) return false; // unpack packet (yet another no-idea-why-it's-needed-cast) C4IDPacket Pkt; C4PacketBase &PktB = Pkt; try { PktB.unpack(rPacket); } catch (StdCompiler::Exception *pExc) { Application.InteractiveThread.ThreadLog("Network: error: Failed to unpack packet id %02x: %s", rPacket.getStatus(), pExc->Msg.getData()); delete pExc; #ifndef _DEBUG pConn->Close(); #endif return false; } // dump packet (network thread only) #if(C4NET2IO_DUMP_LEVEL > 0) if (Config.Network.PacketLogging && fThread && Pkt.getPktType() != PID_Ping && Pkt.getPktType() != PID_Pong && Pkt.getPktType() != PID_NetResData) { // StdStrBuf PacketDump = DecompileToBuf<StdCompilerINIWrite>(mkNamingAdaptrPacket); StdStrBuf PacketHeader = FormatString("HandlePacket: %s by %s:%d (%lu bytes, counter %d)", C4TimeMilliseconds::Now().AsString().getData(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), static_cast<unsigned long>(rPacket.getSize()), pConn->getInPacketCounter()); StdStrBuf Dump = DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(Pkt, PacketHeader.getData())); // Put it directly. The standard functions behind StdBuf.Format seem to choke when you pass them too much data. Application.InteractiveThread.PushEvent(Ev_LogSilent, Dump.GrabPointer()); } #endif // search packet handling data bool fSendToMainThread = false, fHandled = false; for (const C4PktHandlingData *pHData = PktHandlingData; pHData->ID != PID_None; pHData++) if (pHData->ID == rPacket.getStatus()) { // correct thread? if (!pHData->ProcByThread == !fThread) { // connection accepted? if (pHData->AcceptedOnly || pConn->isAccepted() || pConn->isClosed()) { fHandled = true; #if(C4NET2IO_DUMP_LEVEL > 2) C4TimeMilliseconds tStart = C4TimeMilliseconds::Now(); #endif // call handler(s) CallHandlers(pHData->HandlerID, &Pkt, pConn, fThread); #if(C4NET2IO_DUMP_LEVEL > 2) uint32_t iBlockedTime = C4TimeMilliseconds::Now() - tStart; if (fThread && iBlockedTime > 100) { Application.InteractiveThread.ThreadLogS("HandlePacket: ... blocked for %u ms!", iBlockedTime); } #endif } } // transfer to main thread? else if (!pHData->ProcByThread && fThread) { fHandled = true; fSendToMainThread = true; } } // send to main thread? if (fSendToMainThread) { // create data NetEvPacketData *pEvData = new NetEvPacketData; pEvData->Packet.Take(rPacket.Duplicate()); pEvData->Conn = pConn; pConn->AddRef(); // trigger event if (!Application.InteractiveThread.PushEvent(Ev_Net_Packet, pEvData)) Application.InteractiveThread.ThreadLogS("...push event "); } // unhandled? if (!fHandled && !pConn->isClosed()) Application.InteractiveThread.ThreadLog("Network: Unhandled packet (status %02x)", rPacket.getStatus()); // remove connection reference pConn->DelRef(); return fHandled; }