OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4Record.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // scenario record functionality
17 
18 #include "C4Include.h"
19 #include "control/C4Record.h"
20 
21 #include "editor/C4Console.h"
22 #include "control/C4PlayerInfo.h"
23 #include "control/C4GameSave.h"
24 #include "lib/C4Log.h"
25 #include "player/C4Player.h"
26 #include "game/C4Game.h"
27 #include "control/C4GameControl.h"
28 
29 #include "platform/StdFile.h"
30 
31 #define IMMEDIATEREC
32 
34 int DoNoDebugRec=0; // debugrec disable counter
35 
36 void AddDbgRec(C4RecordChunkType eType, const void *pData, int iSize)
37 {
38  ::Control.DbgRec(eType, (const uint8_t *) pData, iSize);
39 }
40 
42 {
44 }
45 
46 C4DebugRecOff::C4DebugRecOff(bool fDoOff) : fDoOff(fDoOff)
47 {
48  if (fDoOff) { DEBUGREC_OFF; }
49 }
50 
52 {
53  if (fDoOff) { DEBUGREC_ON; }
54 }
55 
57 {
59  fDoOff = false;
60 }
61 
63 {
64  // type
65  pComp->Value(mkNamingAdapt(mkIntAdapt(eType), "Type"));
66  // Packet data
67  C4PktBuf::CompileFunc(pComp);
68 }
69 
71  : pCtrl(nullptr)
72 {
73 
74 }
75 
77 {
78  switch (Type)
79  {
80  case RCT_Ctrl: delete pCtrl; pCtrl = nullptr; break;
81  case RCT_CtrlPkt: delete pPkt; pPkt = nullptr; break;
82  case RCT_End: break;
83  case RCT_Frame: break;
84  case RCT_File: delete pFileData; break;
85  default: delete pDbg; pDbg = nullptr; break;
86  }
87 }
88 
90 {
91  pComp->Value(mkNamingAdapt(Frame, "Frame"));
92  pComp->Value(mkNamingAdapt(mkIntAdapt(Type), "Type"));
93  switch (Type)
94  {
95  case RCT_Ctrl: pComp->Value(mkPtrAdaptNoNull(pCtrl)); break;
96  case RCT_CtrlPkt: pComp->Value(mkPtrAdaptNoNull(pPkt)); break;
97  case RCT_End: break;
98  case RCT_Frame: break;
99  case RCT_File: pComp->Value(Filename); pComp->Value(mkPtrAdaptNoNull(pFileData)); break;
100  default: pComp->Value(mkPtrAdaptNoNull(pDbg)); break;
101  }
102 }
103 
105  : fRecording(false), fStreaming(false)
106 {
107 }
108 
110 {
111 }
112 
113 bool C4Record::Start(bool fInitial)
114 {
115  // no double record
116  if (fRecording) return false;
117 
118  // create demos folder
120  return false;
121 
122  // various infos
123  StdStrBuf sDemoFolder(C4CFN_Records);
124  char sScenName[_MAX_FNAME+ 1]; SCopy(GetFilenameOnly(Game.Parameters.Scenario.getFile()), sScenName, _MAX_FNAME);
125 
126  // remove trailing numbers from scenario name (e.g. from savegames) - could we perhaps use C4S.Head.Origin instead...?
127  char *pScenNameEnd = sScenName + SLen(sScenName);
128  while (Inside<char>(*--pScenNameEnd, '0', '9'))
129  if (pScenNameEnd == sScenName)
130  break;
131  pScenNameEnd[1] = 0;
132 
133  // determine index (by total number of records)
134  Index = 1;
137  Index++;
138 
139  // compose record filename
140  sFilename.Format("%s" DirSep "%03i-%s.ocs", Config.AtUserDataPath(sDemoFolder.getData()), Index, sScenName);
141 
142  // log
143  StdStrBuf sLog; sLog.Format(LoadResStr("IDS_PRC_RECORDINGTO"),sFilename.getData());
144  if (Game.FrameCounter) sLog.AppendFormat(" (Frame %d)", Game.FrameCounter);
145  Log(sLog.getData());
146 
147  // save game - this also saves player info list
148  C4GameSaveRecord saveRec(fInitial, Index, Game.Parameters.isLeague());
149  if (!saveRec.Save(sFilename.getData())) return false;
150  saveRec.Close();
151 
152  // unpack group, if neccessary
153  if ( !DirectoryExists(sFilename.getData()) &&
154  !C4Group_UnpackDirectory(sFilename.getData()) )
155  return false;
156 
157  // open control record file
158  char szCtrlRecFilename[_MAX_PATH+1 + _MAX_FNAME];
159  sprintf(szCtrlRecFilename, "%s" DirSep C4CFN_CtrlRec, sFilename.getData());
160  if (!CtrlRec.Create(szCtrlRecFilename)) return false;
161 
162  // open log file in record
163  char szLogRecFilename[_MAX_PATH+1 + _MAX_FNAME];
164  sprintf(szLogRecFilename, "%s" DirSep C4CFN_LogRec, sFilename.getData());
165  if (!LogRec.Create(szLogRecFilename)) return false;
166 
167  // open record group
168  if (!RecordGrp.Open(sFilename.getData()))
169  return false;
170 
171  // record go
172  fStreaming = false;
173  fRecording = true;
174  iLastFrame = 0;
175  return true;
176 }
177 
178 bool C4Record::Stop(StdStrBuf *pRecordName, BYTE *pRecordSHA1)
179 {
180  // safety
181  if (!fRecording) return false;
182  if (!DirectoryExists(sFilename.getData())) return false;
183 
184  // streaming finished
185  StopStreaming();
186 
187  // save desc into record group
188  C4GameSaveRecord saveRec(false, Index, Game.Parameters.isLeague());
189  saveRec.SaveDesc(RecordGrp);
190 
191  // save end player infos into record group
193  RecordGrp.Close();
194 
195  // write last entry and close
196  C4RecordChunkHead Head;
197  Head.iFrm = 37;
198  Head.Type = RCT_End;
199  CtrlRec.Write(&Head, sizeof(Head));
200  CtrlRec.Close();
201 
202  LogRec.Close();
203 
204  // pack group
205  if (!Config.General.DebugRec)
206  if (!C4Group_PackDirectory(sFilename.getData())) return false;
207 
208  // return record data
209  if (pRecordName)
210  pRecordName->Copy(sFilename);
211  if (pRecordSHA1)
212  if (!GetFileSHA1(sFilename.getData(), pRecordSHA1))
213  return false;
214 
215  // ok
216  fRecording = false;
217  return true;
218 }
219 
220 bool C4Record::Rec(const C4Control &Ctrl, int iFrame)
221 {
222  if (!fRecording) return false;
223  // don't record empty control
224  if (!Ctrl.firstPkt()) return true;
225  // create copy
226  C4Control Cpy; Cpy.Copy(Ctrl);
227  // prepare it for record
228  Cpy.PreRec(this);
229  // record it
230  return Rec(iFrame, DecompileToBuf<StdCompilerBinWrite>(Cpy), RCT_Ctrl);
231 }
232 
233 bool C4Record::Rec(C4PacketType eCtrlType, C4ControlPacket *pCtrl, int iFrame)
234 {
235  if (!fRecording) return false;
236  // create copy
237  C4IDPacket Pkt = C4IDPacket(eCtrlType, pCtrl, false); if (!Pkt.getPkt()) return false;
238  C4ControlPacket *pCtrlCpy = static_cast<C4ControlPacket *>(Pkt.getPkt());
239  // prepare for recording
240  pCtrlCpy->PreRec(this);
241  // record it
242  return Rec(iFrame, DecompileToBuf<StdCompilerBinWrite>(Pkt), RCT_CtrlPkt);
243 }
244 
245 bool C4Record::Rec(int iFrame, const StdBuf &sBuf, C4RecordChunkType eType)
246 {
247  // filler chunks (this should never be necessary, though)
248  while (iFrame > int(iLastFrame + 0xff))
249  Rec(iLastFrame + 0xff, StdBuf(), RCT_Frame);
250  // get frame difference
251  uint8_t iFrameDiff = std::max<uint8_t>(0, iFrame - iLastFrame);
252  iLastFrame += iFrameDiff;
253  // create head
254  C4RecordChunkHead Head = { iFrameDiff, uint8_t(eType) };
255  // pack
256  CtrlRec.Write(&Head, sizeof(Head));
257  CtrlRec.Write(sBuf.getData(), sBuf.getSize());
258 #ifdef IMMEDIATEREC
259  // immediate rec: always flush
260  CtrlRec.Flush();
261 #endif
262  // Stream
263  if (fStreaming)
264  Stream(Head, sBuf);
265  return true;
266 }
267 
268 void C4Record::Stream(const C4RecordChunkHead &Head, const StdBuf &sBuf)
269 {
270  if (!fStreaming) return;
271  StreamingData.Append(&Head, sizeof(Head));
272  StreamingData.Append(sBuf.getData(), sBuf.getSize());
273 }
274 
275 bool C4Record::AddFile(const char *szLocalFilename, const char *szAddAs, bool fDelete)
276 {
277  if (!fRecording) return false;
278 
279  // Streaming?
280  if (fStreaming)
281  {
282 
283  // Special stripping for streaming
284  StdCopyStrBuf szFile(szLocalFilename);
285  if (SEqualNoCase(GetExtension(szAddAs), "ocp"))
286  {
287  // Create a copy
288  MakeTempFilename(&szFile);
289  if (!CopyItem(szLocalFilename, szFile.getData()))
290  return false;
291  // Strip it
292  if (!C4Player::Strip(szFile.getData(), true))
293  return false;
294  }
295 
296  // Add to stream
297  if (!StreamFile(szFile.getData(), szAddAs))
298  return false;
299 
300  // Remove temporary file
301  if (szFile != szLocalFilename)
302  EraseItem(szFile.getData());
303  }
304 
305  // Add to record group
306  if (fDelete)
307  {
308  if (!RecordGrp.Move(szLocalFilename, szAddAs))
309  return false;
310  }
311  else
312  {
313  if (!RecordGrp.Add(szLocalFilename, szAddAs))
314  return false;
315  }
316 
317  return true;
318 }
319 
320 bool C4Record::StartStreaming(bool fInitial)
321 {
322  if (!fRecording) return false;
323  if (fStreaming) return false;
324 
325  // Get temporary file name
326  StdCopyStrBuf sTempFilename(sFilename);
327  MakeTempFilename(&sTempFilename);
328 
329  // Save start state (without copy of scenario!)
330  C4GameSaveRecord saveRec(fInitial, Index, Game.Parameters.isLeague(), false);
331  if (!saveRec.Save(sTempFilename.getData())) return false;
332  saveRec.Close();
333 
334  // Add file into stream, delete file
335  fStreaming = true;
336  if (!StreamFile(sTempFilename.getData(), sFilename.getData()))
337  {
338  fStreaming = false;
339  return false;
340  }
341 
342  // Okay
343  EraseFile(sTempFilename.getData());
344  iStreamingPos = 0;
345  return true;
346 }
347 
348 void C4Record::ClearStreamingBuf(unsigned int iAmount)
349 {
350  iStreamingPos += iAmount;
351  if (iAmount == StreamingData.getSize())
352  StreamingData.Clear();
353  else
354  {
355  StreamingData.Move(iAmount, StreamingData.getSize() - iAmount);
356  StreamingData.SetSize(StreamingData.getSize() - iAmount);
357  }
358 }
359 
361 {
362  fStreaming = false;
363 }
364 
365 bool C4Record::StreamFile(const char *szLocalFilename, const char *szAddAs)
366 {
367 
368  // Load file into memory
369  StdBuf FileData;
370  if (!FileData.LoadFromFile(szLocalFilename))
371  return false;
372 
373  // Prepend name
374  StdBuf Packed = DecompileToBuf<StdCompilerBinWrite>(
375  mkInsertAdapt(StdStrBuf(szAddAs), FileData, false));
376 
377  // Add to stream
378  C4RecordChunkHead Head = { 0, RCT_File };
379  Stream(Head, Packed);
380  return true;
381 }
382 
383 // set defaults
384 C4Playback::C4Playback(): Finished(true), fLoadSequential(false)
385 {
386 }
387 
389 {
390  Clear();
391 }
392 
394 {
395  // clean up
396  Clear();
397  iLastSequentialFrame = 0;
398  bool fStrip = false;
399 
400  // open group? Then do some sequential reading for large files
401  // Can't do this when a dump is forced, because the dump needs all data
402  // Also can't do this when stripping is desired
403  fLoadSequential = !rGrp.IsPacked() && !Game.RecordDumpFile.getLength() && !fStrip;
404 
405  // get text record file
406  StdStrBuf TextBuf;
407  if (rGrp.LoadEntryString(C4CFN_CtrlRecText, &TextBuf))
408  {
409  if (!ReadText(TextBuf))
410  return false;
411  }
412  else
413  {
414  // get record file
415  if (fLoadSequential)
416  {
417  if (!rGrp.FindEntry(C4CFN_CtrlRec)) return false;
418  if (!playbackFile.Open(FormatString("%s%c%s", rGrp.GetFullName().getData(), (char) DirectorySeparator, (const char *) C4CFN_CtrlRec).getData())) return false;
419  // forcing first chunk to be read; will call ReadBinary
420  currChunk = chunks.end();
421  if (!NextSequentialChunk())
422  {
423  // empty replay??!
424  LogFatal("Record: Binary read error.");
425  return false;
426  }
427  }
428  else
429  {
430  // non-sequential reading: Just read as a whole
431  StdBuf BinaryBuf;
432  if (rGrp.LoadEntry(C4CFN_CtrlRec, &BinaryBuf))
433  {
434  if (!ReadBinary(BinaryBuf))
435  return false;
436  }
437  else
438  {
439  // file too large? Try sequential loading and parsing
440  /* size_t iSize;
441  if (rGrp.AccessEntry(C4CFN_CtrlRec, &iSize))
442  {
443  CStdFile fOut; fOut.Create(Game.RecordDumpFile.getData());
444  fLoadSequential = true;
445  const size_t iChunkSize = 1024*1024*16; // 16M
446  while (iSize)
447  {
448  size_t iLoadSize = std::min<size_t>(iChunkSize, iSize);
449  BinaryBuf.SetSize(iLoadSize);
450  if (!rGrp.Read(BinaryBuf.getMData(), iLoadSize))
451  {
452  LogFatal("Record: Binary load error!");
453  return false;
454  }
455  iSize -= iLoadSize;
456  if (!ReadBinary(BinaryBuf)) return false;
457  LogF("%d binary remaining", iSize);
458  currChunk = chunks.begin();
459  if (fStrip) Strip();
460  StdStrBuf s(ReWriteText());
461  fOut.WriteString(s.getData());
462  LogF("Wrote %d text bytes (%d binary remaining)", s.getLength(), iSize);
463  chunks.clear();
464  }
465  fOut.Close();
466  fLoadSequential = false;
467  }
468  else*/
469  {
470  // no control data?
471  LogFatal("Record: No control data found!");
472  return false;
473  }
474  }
475  }
476  }
477  // rewrite record
478  if (fStrip) Strip();
480  {
483  else
485  }
486  // reset status
487  currChunk = chunks.begin();
488  Finished = false;
489  // external debugrec file
491  {
493  {
494  if (!DbgRecFile.Create(Config.General.DebugRecExternalFile))
495  {
496  LogFatal(FormatString("DbgRec: Creation of external file \"%s\" failed!", Config.General.DebugRecExternalFile).getData());
497  return false;
498  }
499  else LogF("DbgRec: Writing to \"%s\"...", Config.General.DebugRecExternalFile);
500  }
501  else
502  {
503  if (!DbgRecFile.Open(Config.General.DebugRecExternalFile))
504  {
505  LogFatal(FormatString("DbgRec: Opening of external file \"%s\" failed!", Config.General.DebugRecExternalFile).getData());
506  return false;
507  }
508  else LogF("DbgRec: Checking against \"%s\"...", Config.General.DebugRecExternalFile);
509  }
510  }
511  // ok
512  return true;
513 }
514 
516 {
517  // sequential reading: Take over rest from last buffer
518  const StdBuf *pUseBuf; uint32_t iFrame = 0;
519  if (fLoadSequential)
520  {
521  sequentialBuffer.Append(Buf);
522  pUseBuf = &sequentialBuffer;
523  iFrame = iLastSequentialFrame;
524  }
525  else
526  pUseBuf = &Buf;
527  // get buffer data
528  size_t iPos = 0; bool fFinished = false;
529  do
530  {
531  // unpack header
532  if (pUseBuf->getSize() - iPos < sizeof(C4RecordChunkHead)) break;
533  const C4RecordChunkHead *pHead = getBufPtr<C4RecordChunkHead>(*pUseBuf, iPos);
534  // get chunk
535  iPos += sizeof(C4RecordChunkHead);
536  StdBuf Chunk = pUseBuf->getPart(iPos, pUseBuf->getSize() - iPos);
537  // Create entry
538  C4RecordChunk c;
539  c.Frame = (iFrame + pHead->iFrm);
540  c.Type = pHead->Type;
541  // Unpack data
542  try
543  {
544  // Initialize compiler
545  StdCompilerBinRead Compiler;
546  Compiler.setInput(Chunk.getRef());
547  Compiler.Begin();
548  // Read chunk
549  switch (pHead->Type)
550  {
551  case RCT_Ctrl:
552  Compiler.Value(mkPtrAdaptNoNull(c.pCtrl));
553  break;
554  case RCT_CtrlPkt:
555  Compiler.Value(mkPtrAdaptNoNull(c.pPkt));
556  break;
557  case RCT_End:
558  fFinished = true;
559  break;
560  case RCT_File:
561  Compiler.Value(c.Filename);
562  Compiler.Value(mkPtrAdaptNoNull(c.pFileData));
563  break;
564  default:
565  // debugrec
566  if (pHead->Type >= 0x80)
567  Compiler.Value(mkPtrAdaptNoNull(c.pDbg));
568  }
569  // Advance over data
570  Compiler.End();
571  iPos += Compiler.getPosition();
572  }
573  catch (StdCompiler::EOFException *pEx)
574  {
575  // This is to be expected for sequential reading
576  if (fLoadSequential)
577  {
578  iPos -= sizeof(C4RecordChunkHead);
579  delete pEx;
580  break;
581  }
582  LogF("Record: Binary unpack error: %s", pEx->Msg.getData());
583  c.Delete();
584  delete pEx;
585  return false;
586  }
587  catch (StdCompiler::Exception *pEx)
588  {
589  LogF("Record: Binary unpack error: %s", pEx->Msg.getData());
590  c.Delete();
591  delete pEx;
592  return false;
593  }
594  // Add to list
595  chunks.push_back(c); c.pPkt = nullptr;
596  iFrame = c.Frame;
597  }
598  while (!fFinished);
599  // erase everything but the trailing part from sequential buffer
600  if (fLoadSequential)
601  {
602  if (iPos >= sequentialBuffer.getSize())
603  sequentialBuffer.Clear();
604  else if (iPos)
605  {
606  sequentialBuffer.Move(iPos, sequentialBuffer.getSize() - iPos);
607  sequentialBuffer.Shrink(iPos);
608  }
609  // remember frame
610  iLastSequentialFrame = iFrame;
611  }
612  return true;
613 }
614 
616 {
617  return CompileFromBuf_LogWarn<StdCompilerINIRead>(mkNamingAdapt(mkSTLContainerAdapt(chunks), "Rec"), Buf, C4CFN_CtrlRecText);
618 }
619 
621 {
622  assert(currChunk != chunks.end());
623  ++currChunk;
624  if (currChunk != chunks.end()) return;
625  // end of all chunks if not loading sequential here
626  if (!fLoadSequential) return;
627  // otherwise, get next few chunks
628  for (chunks_t::iterator i = chunks.begin(); i != chunks.end(); i++) i->Delete();
629  chunks.clear(); currChunk = chunks.end();
631 }
632 
634 {
635  StdBuf BinaryBuf; size_t iRealSize;
636  BinaryBuf.New(4096);
637  // load data until a chunk could be filled
638  for (;;)
639  {
640  iRealSize = 0;
641  playbackFile.Read(BinaryBuf.getMData(), 4096, &iRealSize);
642  if (!iRealSize) return false;
643  BinaryBuf.SetSize(iRealSize);
644  if (!ReadBinary(BinaryBuf)) return false;
645  // okay, at least one chunk has been read!
646  if (chunks.size())
647  {
648  currChunk = chunks.begin();
649  return true;
650  }
651  }
652  // playback file reading failed - looks like we're done
653  return false;
654 }
655 
657 {
658  // Would work, too, but is currently too slow due to bad buffering inside StdCompilerINIWrite:
659  // return DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(mkSTLContainerAdapt(chunks), "Rec"));
660  StdStrBuf Output;
661  for (chunks_t::const_iterator i = chunks.begin(); i != chunks.end(); i++)
662  {
663  Output.Append(static_cast<const StdStrBuf&>(DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(mkDecompileAdapt(*i), "Rec"))));
664  Output.Append("\n\n");
665  }
666  return Output;
667 }
668 
670 {
671  const int OUTPUT_GROW = 16 * 1024;
672  StdBuf Output; int iPos = 0;
673  bool fFinished = false;
674  int32_t iFrame = 0;
675  for (chunks_t::const_iterator i = chunks.begin(); !fFinished && i != chunks.end(); i++)
676  {
677  // Check frame difference
678  if (i->Frame - iFrame < 0 || i->Frame - iFrame > 0xff)
679  LogF("ERROR: Invalid frame difference between chunks (0-255 allowed)! Data will be invalid!");
680  // Pack data
681  StdBuf Chunk;
682  try
683  {
684  switch (i->Type)
685  {
686  case RCT_Ctrl:
687  Chunk = DecompileToBuf<StdCompilerBinWrite>(*i->pCtrl);
688  break;
689  case RCT_CtrlPkt:
690  Chunk = DecompileToBuf<StdCompilerBinWrite>(*i->pPkt);
691  break;
692  case RCT_End:
693  fFinished = true;
694  break;
695  default: // debugrec
696  if (i->pDbg)
697  Chunk = DecompileToBuf<StdCompilerBinWrite>(*i->pDbg);
698  break;
699  }
700  }
701  catch (StdCompiler::Exception *pEx)
702  {
703  LogF("Record: Binary unpack error: %s", pEx->Msg.getData());
704  delete pEx;
705  return StdBuf();
706  }
707  // Grow output
708  while (Output.getSize() - iPos < sizeof(C4RecordChunkHead) + Chunk.getSize())
709  Output.Grow(OUTPUT_GROW);
710  // Write header
711  C4RecordChunkHead *pHead = getMBufPtr<C4RecordChunkHead>(Output, iPos);
712  pHead->Type = i->Type;
713  pHead->iFrm = i->Frame - iFrame;
714  iPos += sizeof(C4RecordChunkHead);
715  iFrame = i->Frame;
716  // Write chunk
717  Output.Write(Chunk, iPos);
718  iPos += Chunk.getSize();
719  }
720  Output.SetSize(iPos);
721  return Output;
722 }
723 
725 {
726  // Strip what?
727  const bool fStripPlayers = false;
728  const bool fStripSyncChecks = false;
729  const bool fStripDebugRec = true;
730  const bool fCheckCheat = false;
731  const bool fStripMessages = true;
732  const int32_t iEndFrame = -1;
733  // Iterate over chunk list
734  for (chunks_t::iterator i = chunks.begin(); i != chunks.end(); )
735  {
736  // Strip rest of record?
737  if (iEndFrame >= 0 && i->Frame > iEndFrame)
738  {
739  // Remove this and all remaining chunks
740  while (i != chunks.end())
741  {
742  i->Delete();
743  i = chunks.erase(i);
744  }
745  // Push new End-Chunk
746  C4RecordChunk EndChunk;
747  EndChunk.Frame = iEndFrame;
748  EndChunk.Type = RCT_End;
749  chunks.push_back(EndChunk);
750  // Done
751  break;
752  }
753  switch (i->Type)
754  {
755  case RCT_Ctrl:
756  {
757  // Iterate over controls
758  C4Control *pCtrl = i->pCtrl;
759  for (C4IDPacket *pPkt = pCtrl->firstPkt(), *pNext; pPkt; pPkt = pNext)
760  {
761  pNext = pCtrl->nextPkt(pPkt);
762  switch (pPkt->getPktType())
763  {
764  // Player join: Strip player file (if possible)
765  case CID_JoinPlr:
766  if (fStripPlayers)
767  {
768  C4ControlJoinPlayer *pJoinPlr = static_cast<C4ControlJoinPlayer *>(pPkt->getPkt());
769  pJoinPlr->Strip();
770  }
771  break;
772  // EM commands: May be cheats, so log them
773  case CID_Script:
774  case CID_EMMoveObj:
775  case CID_EMDrawTool:
776  case CID_ReInitScenario:
777  case CID_EditGraph:
778  if (fCheckCheat) Log(DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(*pPkt, FormatString("Frame %d", i->Frame).getData())).getData());
779  break;
780  // Strip sync check
781  case CID_SyncCheck:
782  if (fStripSyncChecks)
783  {
784  i->pCtrl->Remove(pPkt);
785  }
786  break;
787  default:
788  // TODO
789  break;
790  }
791  }
792  // Strip empty control lists (always)
793  if (!pCtrl->firstPkt())
794  {
795  i->Delete();
796  i = chunks.erase(i);
797  }
798  else
799  i++;
800  }
801  break;
802  case RCT_CtrlPkt:
803  {
804  bool fStripThis=false;
805  switch (i->pPkt->getPktType())
806  {
807  // EM commands: May be cheats, so log them
808  case CID_Script:
809  case CID_EMMoveObj:
810  case CID_EMDrawTool:
811  case CID_ReInitScenario:
812  case CID_EditGraph:
813  if (fCheckCheat) Log(DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(*i->pPkt, FormatString("Frame %d", i->Frame).getData())).getData());
814  break;
815  // Strip some stuff
816  case CID_SyncCheck:
817  if (fStripSyncChecks) fStripThis = true;
818  break;
819  case CID_Message:
820  if (fStripMessages) fStripThis=true;
821  break;
822  default:
823  // TODO
824  break;
825  }
826  if (fStripThis)
827  {
828  i->Delete();
829  i = chunks.erase(i);
830  }
831  else i++;
832  }
833  break;
834  case RCT_End:
835  i++;
836  break;
837  default:
838  // Strip debugrec
839  if (fStripDebugRec)
840  {
841  i->Delete();
842  i = chunks.erase(i);
843  }
844  else
845  i++;
846  }
847  }
848 }
849 
850 
851 bool C4Playback::ExecuteControl(C4Control *pCtrl, int iFrame)
852 {
853  // still playbacking?
854  if (currChunk == chunks.end()) return false;
855  if (Finished) { Finish(); return false; }
856  if (Config.General.DebugRec)
857  {
858  if (DebugRec.firstPkt())
859  DebugRecError("Debug rec overflow!");
860  DebugRec.Clear();
861  }
862  // return all control until this frame
863  while (currChunk != chunks.end() && currChunk->Frame <= iFrame)
864  {
865  switch (currChunk->Type)
866  {
867  case RCT_Ctrl:
868  pCtrl->Append(*currChunk->pCtrl);
869  break;
870 
871  case RCT_CtrlPkt:
872  {
873  C4IDPacket Packet(*currChunk->pPkt);
874  pCtrl->Add(Packet.getPktType(), static_cast<C4ControlPacket *>(Packet.getPkt()));
875  Packet.Default();
876  break;
877  }
878 
879  case RCT_End:
880  // end of playback; stop it!
881  Finished=true;
882  break;
883 
884  default: // expect it to be debug rec
885  if (Config.General.DebugRec)
886  {
887  // append to debug rec buffer
888  if (currChunk->pDbg)
889  {
890  DebugRec.Add(CID_DebugRec, currChunk->pDbg);
891  // the debugrec buffer is now responsible for deleting the packet
892  currChunk->pDbg = nullptr;
893  }
894  break;
895  }
896  }
897  // next chunk
898  NextChunk();
899  }
900  return true;
901 }
902 
903 void C4Playback::Finish()
904 {
905  Clear();
906  // finished playback: end game
907  if (Console.Active)
908  {
909  ++Game.HaltCount;
911  }
912  else
913  {
914  Game.DoGameOver();
915  }
916  // finish playback: enable controls
918 }
919 
921 {
922  // free stuff
923  for (chunks_t::iterator i = chunks.begin(); i != chunks.end(); i++) i->Delete();
924  chunks.clear(); currChunk = chunks.end();
925  playbackFile.Close();
926  sequentialBuffer.Clear();
927  fLoadSequential = false;
928  if (Config.General.DebugRec)
929  {
930  C4IDPacket *pkt;
931  while ((pkt = DebugRec.firstPkt())) DebugRec.Delete(pkt);
933  DbgRecFile.Close();
934  }
935  // done
936  Finished = true;
937 }
938 
940 {
941  switch (eType)
942  {
943  case RCT_Ctrl: return "Ctrl"; // control
944  case RCT_CtrlPkt: return "CtrlPkt"; // control packet
945  case RCT_Frame: return "Frame"; // beginning frame
946  case RCT_End: return "End"; // --- the end ---
947  case RCT_Log: return "Log"; // log message
948  case RCT_File: return "File"; // file data
949  // DEBUGREC
950  case RCT_Block: return "Block"; // point in Game::Execute
951  case RCT_SetPix: return "SetPix"; // set landscape pixel
952  case RCT_ExecObj: return "ExecObj"; // exec object
953  case RCT_Random: return "Random"; // Random()-call
954  case RCT_Rn3: return "Rn3"; // Rn3()-call
955  case RCT_MMC: return "MMC"; // create MassMover
956  case RCT_MMD: return "MMD"; // destroy MassMover
957  case RCT_CrObj: return "CrObj"; // create object
958  case RCT_DsObj: return "DsObj"; // remove object
959  case RCT_GetPix: return "GetPix"; // get landscape pixel; let the Gigas flow!
960  case RCT_RotVtx1: return "RotVtx1"; // before shape is rotated
961  case RCT_RotVtx2: return "RotVtx2"; // after shape is rotated
962  case RCT_ExecPXS: return "ExecPXS"; // execute pxs system
963  case RCT_Sin: return "Sin"; // sin by Shape-Rotation
964  case RCT_Cos: return "Cos"; // cos by Shape-Rotation
965  case RCT_Map: return "Map"; // map dump
966  case RCT_Ls: return "Ls"; // complete landscape dump!
967  case RCT_MCT1: return "MCT1"; // MapCreatorS2: before transformation
968  case RCT_MCT2: return "MCT2"; // MapCreatorS2: after transformation
969  case RCT_AulFunc: return "AulFunc"; // script function call
970  case RCT_ObjCom: return "ObjCom"; // object com
971  case RCT_PlrCom: return "PlrCom"; // player com
972  case RCT_PlrInCom: return "PlrInCom"; // player InCom
973  case RCT_MatScan: return "MatScan"; // landscape scan execute
974  case RCT_MatScanDo: return "MatScanDo"; // landscape scan mat change
975  case RCT_Area: return "Area"; // object area change
976  case RCT_MenuAdd: return "MenuAdd"; // add menu item
977  case RCT_MenuAddC: return "MenuAddC"; // add menu item: Following commands
978  case RCT_OCF: return "OCF"; // OCF setting of updating
979  case RCT_DirectExec: return "DirectExec"; // a DirectExec-script
980  case RCT_Definition: return "Definition"; // Definition callback
981 
982  case RCT_Custom: return "Custom"; // varies
983 
984 case RCT_Undefined: default: return "Undefined";
985  };
986 }
987 
989 {
990  StdStrBuf r;
991  switch (eType)
992  {
993  case RCT_AulFunc: r.Ref(reinterpret_cast<const char*>(RawData.getData()), RawData.getSize()-1);
994  break;
995  default:
996  for (unsigned int i=0; i<RawData.getSize(); ++i)
997  r.AppendFormat("%02x ", (uint32_t) *getBufPtr<uint8_t>(RawData, i));
998  break;
999  }
1000  return r;
1001 }
1002 
1003 void C4Playback::Check(C4RecordChunkType eType, const uint8_t *pData, int iSize)
1004 {
1005  // only if enabled
1006  if (DoNoDebugRec>0) return;
1007  if (Game.FrameCounter < DEBUGREC_START_FRAME) return;
1008 
1009  C4PktDebugRec PktInReplay;
1010  bool fHasPacketFromHead = false;
1012  {
1014  {
1015  // writing of external debugrec file
1016  DbgRecFile.Write(&eType, sizeof eType);
1017  int32_t iSize32 = iSize;
1018  DbgRecFile.Write(&iSize32, sizeof iSize32);
1019  DbgRecFile.Write(pData, iSize);
1020  return;
1021  }
1022  else
1023  {
1024  int32_t iSize32 = 0;
1025  C4RecordChunkType eTypeRec = RCT_Undefined;
1026  DbgRecFile.Read(&eTypeRec, sizeof eTypeRec);
1027  DbgRecFile.Read(&iSize32, sizeof iSize32);
1028  if (iSize32)
1029  {
1030  StdBuf buf;
1031  buf.SetSize(iSize32);
1032  DbgRecFile.Read(buf.getMData(), iSize32);
1033  PktInReplay = C4PktDebugRec(eTypeRec, buf);
1034  }
1035  }
1036  }
1037  else
1038  {
1039  // check debug rec in list
1040  C4IDPacket *pkt;
1041  if ((pkt = DebugRec.firstPkt()))
1042  {
1043  // copy from list
1044  PktInReplay = *static_cast<C4PktDebugRec *>(pkt->getPkt());
1045  DebugRec.Delete(pkt);
1046  }
1047  else
1048  {
1049  // special sync check skip...
1050  while (currChunk != chunks.end() && currChunk->Type == RCT_CtrlPkt)
1051  {
1052  C4IDPacket Packet(*currChunk->pPkt);
1053  C4ControlPacket *pCtrlPck = static_cast<C4ControlPacket *>(Packet.getPkt());
1054  assert(!pCtrlPck->Sync());
1055  ::Control.ExecControlPacket(Packet.getPktType(), pCtrlPck);
1056  NextChunk();
1057  }
1058  // record end?
1059  if (currChunk == chunks.end() || currChunk->Type == RCT_End || Finished)
1060  {
1061  Log("DebugRec end: All in sync!");
1062  ++DoNoDebugRec;
1063  return;
1064  }
1065  // unpack directly from head
1066  if (currChunk->Type != eType)
1067  {
1068  DebugRecError(FormatString("Playback type %x, this type %x", currChunk->Type, eType).getData());
1069  return;
1070  }
1071  if (currChunk->pDbg)
1072  PktInReplay = *currChunk->pDbg;
1073 
1074  fHasPacketFromHead = true;
1075  }
1076  }
1077  // record end?
1078  if (PktInReplay.getType() == RCT_End)
1079  {
1080  Log("DebugRec end: All in sync (2)!");
1081  ++DoNoDebugRec;
1082  return;
1083  }
1084  // replay packet is unpacked to PktInReplay now; check it
1085  if (PktInReplay.getType() != eType)
1086  {
1087  DebugRecError(FormatString("Type %s != %s", GetRecordChunkTypeName(PktInReplay.getType()), GetRecordChunkTypeName(eType)).getData());
1088  return;
1089  }
1090  if (PktInReplay.getSize() != unsigned(iSize))
1091  {
1092  DebugRecError(FormatString("Size %d != %d", (int) PktInReplay.getSize(), (int) iSize).getData());
1093  }
1094  // check packet data
1095  if (memcmp(PktInReplay.getData(), pData, iSize))
1096  {
1097  StdStrBuf sErr;
1098  sErr.Format("DbgRecPkt Type %s, size %d", GetRecordChunkTypeName(eType), iSize);
1099  sErr.Append(" Replay: ");
1100  StdBuf replay(PktInReplay.getData(), PktInReplay.getSize());
1101  sErr.Append(GetDbgRecPktData(eType, replay));
1102  sErr.Append(" Here: ");
1103  StdBuf here(pData, iSize);
1104  sErr.Append(GetDbgRecPktData(eType, here));
1105  DebugRecError(sErr.getData());
1106  }
1107  // packet is fine, jump over it
1108  if (fHasPacketFromHead)
1109  NextChunk();
1110 }
1111 
1112 void C4Playback::DebugRecError(const char *szError)
1113 {
1114  LogF("Playback error: %s", szError);
1116 }
1117 
1118 bool C4Playback::StreamToRecord(const char *szStream, StdStrBuf *pRecordFile)
1119 {
1120 
1121  // Load data
1122  StdBuf CompressedData;
1123  Log("Reading stream...");
1124  if (!CompressedData.LoadFromFile(szStream))
1125  return false;
1126 
1127  // Decompress
1128  unsigned long iStreamSize = CompressedData.getSize() * 5;
1129  StdBuf StreamData; StreamData.New(iStreamSize);
1130  while (true)
1131  {
1132 
1133  // Initialize stream
1134  z_stream strm;
1135  ZeroMem(&strm, sizeof strm);
1136  strm.next_in = getMBufPtr<BYTE>(CompressedData);
1137  strm.avail_in = CompressedData.getSize();
1138  strm.next_out = getMBufPtr<BYTE>(StreamData);
1139  strm.avail_out = StreamData.getSize();
1140 
1141  // Decompress
1142  if (inflateInit(&strm) != Z_OK)
1143  return false;
1144  int ret = inflate(&strm, Z_FINISH);
1145  if (ret == Z_OK)
1146  {
1147  inflateEnd(&strm);
1148  break;
1149  }
1150  if (ret != Z_BUF_ERROR)
1151  return false;
1152 
1153  // All input consumed?
1154  iStreamSize = strm.total_out;
1155  if (strm.avail_in == 0)
1156  {
1157  Log("Stream data incomplete, using as much data as possible");
1158  break;
1159  }
1160 
1161  // Larger buffer needed
1162  StreamData.Grow(CompressedData.getSize());
1163  iStreamSize = StreamData.getSize();
1164  }
1165  StreamData.SetSize(iStreamSize);
1166 
1167  // Parse
1168  C4Playback Playback;
1169  Playback.ReadBinary(StreamData);
1170  LogF("Got %lu chunks from stream", static_cast<unsigned long>(Playback.chunks.size()));
1171 
1172  // Get first chunk, which must contain the initial
1173  chunks_t::iterator chunkIter = Playback.chunks.begin();
1174  if (chunkIter == Playback.chunks.end() || chunkIter->Type != RCT_File)
1175  return false;
1176 
1177  // Get initial chunk, go over file name
1178  StdBuf InitialData = *chunkIter->pFileData;
1179 
1180  // Put to temporary file and unpack
1181  char szInitial[_MAX_PATH+1] = "~initial.tmp";
1182  MakeTempFilename(szInitial);
1183  if (!InitialData.SaveToFile(szInitial) ||
1184  !C4Group_UnpackDirectory(szInitial))
1185  return false;
1186 
1187  // Load Scenario.txt from Initial
1188  C4Group Grp; C4Scenario Initial;
1189  if (!Grp.Open(szInitial) ||
1190  !Initial.Load(Grp) ||
1191  !Grp.Close())
1192  return false;
1193 
1194  // Copy original scenario
1195  const char *szOrigin = Initial.Head.Origin.getData();
1196  char szRecord[_MAX_PATH + 1];
1197  SCopy(szStream, szRecord, _MAX_PATH);
1198  if (GetExtension(szRecord))
1199  *(GetExtension(szRecord) - 1) = 0;
1200  SAppend(".ocs", szRecord, _MAX_PATH);
1201  LogF("Original scenario is %s, creating %s.", szOrigin, szRecord);
1202  if (!C4Group_CopyItem(szOrigin, szRecord, false, false))
1203  return false;
1204 
1205  // Merge initial
1206  if (!Grp.Open(szRecord) ||
1207  !Grp.Merge(szInitial))
1208  return false;
1209 
1210  // Process other files in stream
1211  chunkIter->Delete();
1212  chunkIter = Playback.chunks.erase(chunkIter);
1213  while (chunkIter != Playback.chunks.end())
1214  if (chunkIter->Type == RCT_File)
1215  {
1216  LogF("Inserting %s...", chunkIter->Filename.getData());
1217  StdStrBuf Temp; Temp.Copy(chunkIter->Filename);
1218  MakeTempFilename(&Temp);
1219  if (!chunkIter->pFileData->SaveToFile(Temp.getData()))
1220  return false;
1221  if (!Grp.Move(Temp.getData(), chunkIter->Filename.getData()))
1222  return false;
1223  chunkIter = Playback.chunks.erase(chunkIter);
1224  }
1225  else
1226  chunkIter++;
1227 
1228  // Write record data
1229  StdBuf RecordData = Playback.ReWriteBinary();
1230  if (!Grp.Add(C4CFN_CtrlRec, RecordData, false, true))
1231  return false;
1232 
1233  // Done
1234  Log("Writing record file...");
1235  Grp.Close();
1236  pRecordFile->Copy(szRecord);
1237  return true;
1238 }
const char * getData() const
Definition: StdBuf.h:450
bool DoGameOver()
Definition: C4Game.cpp:3099
bool FindEntry(const char *szWildCard, StdStrBuf *sFileName=nullptr, size_t *iSize=nullptr)
Definition: C4Group.cpp:1774
bool Close(StdBuf **ppMemory=nullptr)
Definition: CStdFile.cpp:155
virtual void End()
Definition: StdCompiler.h:165
C4IDPacket * firstPkt() const
Definition: C4Control.h:78
StdCopyStrBuf Origin
Definition: C4Scenario.h:80
C4RecordChunkType getType() const
Definition: C4Record.h:238
void setInput(InT &&In)
Definition: StdCompiler.h:493
const void * getData() const
Definition: StdBuf.h:107
C4Config Config
Definition: C4Config.cpp:831
Definition: StdBuf.h:37
virtual void CompileFunc(StdCompiler *pComp)
Definition: C4Record.cpp:62
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:122
StdStrBuf RecordDumpFile
Definition: C4Game.h:126
C4Console Console
Definition: C4Globals.cpp:45
virtual StdStrBuf getPosition() const
void Add(C4IDPacket *pPkt)
Definition: C4Packet2.cpp:297
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:227
bool CopyItem(const char *szSource, const char *szTarget, bool fResetAttributes)
Definition: StdFile.cpp:841
void PreRec(C4Record *pRecord) const
Definition: C4Control.cpp:138
C4Game Game
Definition: C4Globals.cpp:52
bool Rec(const C4Control &Ctrl, int iFrame)
Definition: C4Record.cpp:220
bool Merge(const char *szFolders)
Definition: C4Group.cpp:1229
bool Create(const char *szFileName, bool fCompressed=false, bool fExecutable=false, bool fMemory=false)
Definition: CStdFile.cpp:53
C4ConfigGeneral General
Definition: C4Config.h:252
#define sprintf
Definition: Standard.h:171
const void * getData() const
Definition: C4PacketBase.h:230
bool EraseFile(const char *szFilename)
Definition: StdFile.cpp:495
virtual void CompileFunc(StdCompiler *pComp)
Definition: C4Record.cpp:89
void Clear()
Definition: StdBuf.h:198
bool LoadFromFile(const char *szFile)
Definition: StdBuf.cpp:39
const char * GetRecordChunkTypeName(C4RecordChunkType eType)
Definition: C4Record.cpp:939
void Add(C4PacketType eType, C4ControlPacket *pCtrl)
Definition: C4Control.h:82
bool GetFileSHA1(const char *szFilename, BYTE *pSHA1)
Definition: CStdFile.cpp:385
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:177
void ExecControlPacket(C4PacketType eCtrlType, class C4ControlPacket *pPkt)
C4PacketType
Definition: C4PacketBase.h:76
StdBuf ReWriteBinary()
Definition: C4Record.cpp:669
bool CreateSaveFolder(const char *strDirectory, const char *strLanguageTitle)
Definition: C4Config.cpp:570
void Delete()
Definition: C4Record.cpp:76
void Clear()
Definition: C4Record.cpp:56
bool LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize=nullptr, int iAppendZeros=0)
Definition: C4Group.cpp:1893
uint8_t BYTE
uint8_t Type
Definition: C4Record.h:106
char DebugRecExternalFile[_MAX_PATH+1]
Definition: C4Config.h:65
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:181
bool C4Group_UnpackDirectory(const char *szFilename)
Definition: C4Group.cpp:306
#define DEBUGREC_ON
Definition: C4Record.h:29
CStdFile DbgRecFile
Definition: C4Record.cpp:33
#define _MAX_PATH
void Grow(size_t iGrow)
Definition: StdBuf.h:179
void StopStreaming()
Definition: C4Record.cpp:360
const char * GetFilenameOnly(const char *strFilename)
Definition: StdFile.cpp:70
size_t SLen(const char *sptr)
Definition: Standard.h:78
C4GameParameters & Parameters
Definition: C4Game.h:69
size_t getSize() const
Definition: StdBuf.h:109
void DbgRec(C4RecordChunkType eType, const uint8_t *pData=nullptr, size_t iSize=0)
void NextChunk()
Definition: C4Record.cpp:620
void SetSize(size_t inSize)
Definition: StdBuf.h:212
bool Save(C4Group &hGroup, const char *szToFile)
StdBuf getPart(size_t iStart, size_t inSize) const
Definition: StdBuf.h:115
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:197
StdNamingAdapt< T > mkNamingAdapt(T &&rValue, const char *szName)
Definition: StdAdaptors.h:93
C4RecordChunkType
Definition: C4Record.h:44
int DoNoDebugRec
Definition: C4Record.cpp:34
int32_t FrameCounter
Definition: C4Game.h:130
bool ReadText(const StdStrBuf &Buf)
Definition: C4Record.cpp:615
bool Write(const void *pBuffer, int iSize)
Definition: CStdFile.cpp:244
static bool Strip(const char *szFilename, bool fAggressive)
Definition: C4Player.cpp:961
virtual void PreRec(C4Record *pRecord)
Definition: C4Control.h:50
bool SaveDesc(C4Group &hToGroup)
Definition: C4GameSave.cpp:234
bool Stop(StdStrBuf *pRecordName=nullptr, BYTE *pRecordSHA1=nullptr)
Definition: C4Record.cpp:178
bool Close()
Definition: C4GameSave.cpp:446
bool SaveToFile(const char *szFile) const
Definition: StdBuf.cpp:101
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
StdStrBuf ReWriteText()
Definition: C4Record.cpp:656
C4GameControl Control
bool Move(const char *szFile, const char *szAddAs)
Definition: C4Group.cpp:1325
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
StdDecompileAdapt< T > mkDecompileAdapt(const T &rValue)
Definition: StdAdaptors.h:154
#define C4CFN_CtrlRecText
Definition: C4Components.h:78
bool Load(C4Group &hGroup, bool fLoadSection=false, bool suppress_errors=false)
Definition: C4Scenario.cpp:92
bool Open(C4Group &rGrp)
Definition: C4Record.cpp:393
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:527
bool C4Group_CopyItem(const char *szSource, const char *szTarget1, bool fNoSort, bool fResetAttributes)
Definition: C4Group.cpp:100
StdPtrAdapt< T > mkPtrAdaptNoNull(T *&rpObj)
Definition: StdAdaptors.h:604
void Check(C4RecordChunkType eType, const uint8_t *pData, int iSize)
Definition: C4Record.cpp:1003
void AddDbgRec(C4RecordChunkType eType, const void *pData, int iSize)
Definition: C4Record.cpp:36
StdStrBuf GetDbgRecPktData(C4RecordChunkType eType, const StdBuf &RawData)
Definition: C4Record.cpp:988
virtual void Begin()
bool Close()
Definition: C4Group.cpp:755
void Shrink(size_t iShrink)
Definition: StdBuf.h:188
void Clear()
Definition: C4Record.cpp:920
void Copy(const C4Control &Ctrl)
Definition: C4Control.h:86
#define BREAKPOINT_HERE
int32_t Frame
Definition: C4Record.h:105
bool Read(void *pBuffer, size_t iSize) override
Definition: CStdFile.h:61
bool LogFatal(const char *szMessage)
Definition: C4Log.cpp:230
StdInsertAdapt< T, I > mkInsertAdapt(T &&rObj, I &&rIns, bool fBefore=true)
Definition: StdAdaptors.h:435
void Clear()
Definition: C4Packet2.cpp:335
void Delete(C4IDPacket *pPkt)
Definition: C4Packet2.cpp:362
#define _MAX_FNAME
StdCopyStrBuf Filename
Definition: C4Record.h:114
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:131
void Value(const T &rStruct)
Definition: StdCompiler.h:170
#define C4CFN_RecPlayerInfos
Definition: C4Components.h:126
void New(size_t inSize)
Definition: StdBuf.h:154
bool SaveToFile(const char *szFile) const
Definition: StdBuf.cpp:60
virtual void CompileFunc(StdCompiler *pComp)
Definition: C4Packet2.cpp:189
C4RecordChunkType eType
Definition: C4Record.h:231
bool Start(bool fInitial)
Definition: C4Record.cpp:113
void Ref(const char *pnData)
Definition: StdBuf.h:463
void Append(const void *pnData, size_t inSize)
Definition: StdBuf.h:262
bool DirectoryExists(const char *szFilename)
Definition: StdFile.cpp:694
C4PlayerInfoList & PlayerInfos
Definition: C4Game.h:73
void Strip()
Definition: C4Record.cpp:724
size_t getSize() const
Definition: C4PacketBase.h:229
StdBuf getRef() const
Definition: StdBuf.h:279
bool LoadEntryString(const char *szEntryName, StdStrBuf *Buf)
Definition: C4Group.cpp:1932
bool ExecuteControl(C4Control *pCtrl, int iFrame)
Definition: C4Record.cpp:851
StdSTLContainerAdapt< C > mkSTLContainerAdapt(C &rTarget, StdCompiler::Sep eSep=StdCompiler::SEP_SEP)
Definition: StdAdaptors.h:679
bool Open(const char *szFileName, bool fCompressed=false)
Definition: CStdFile.cpp:99
C4IDPacket * nextPkt(C4IDPacket *pPkt) const
Definition: C4Control.h:79
bool EraseItem(const char *szItemName)
Definition: StdFile.cpp:819
const char * AtUserDataPath(const char *szFilename)
Definition: C4Config.cpp:524
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:384
bool UpdateHaltCtrls(bool fHalt)
Definition: C4ConsoleGUI.h:153
const char * getFile() const
bool Active
Definition: C4Window.h:278
bool Log(const char *szMessage)
Definition: C4Log.cpp:195
void Append(const C4Control &Ctrl)
Definition: C4Control.h:85
int32_t DebugRecWrite
Definition: C4Config.h:63
bool isLeague() const
bool Add(const char *szFile, const char *szAddAs)
Definition: C4Group.cpp:1316
int Index
Definition: C4Record.h:259
#define C4CFN_ScenarioFiles
Definition: C4Components.h:174
void ClearStreamingBuf(unsigned int iAmount)
Definition: C4Record.cpp:348
bool IsPacked() const
Definition: C4Group.cpp:2009
#define DEBUGREC_OFF
Definition: C4Record.h:28
size_t getLength() const
Definition: StdBuf.h:453
void Move(size_t iFrom, size_t inSize, size_t iTo=0)
Definition: StdBuf.h:167
int32_t DebugRec
Definition: C4Config.h:61
#define C4CFN_Records
Definition: C4Components.h:36
bool NextSequentialChunk()
Definition: C4Record.cpp:633
#define C4CFN_CtrlRec
Definition: C4Components.h:77
int32_t HaltCount
Definition: C4Game.h:114
std::enable_if< std::is_pod< T >::value >::type ZeroMem(T *lpMem, size_t dwSize)
Definition: Standard.h:63
C4SHead Head
Definition: C4Scenario.h:230
void MakeTempFilename(char *szFilename)
Definition: StdFile.cpp:333
#define DirSep
#define C4CFN_LogRec
Definition: C4Components.h:79
virtual bool Sync() const
Definition: C4Control.h:55
bool AddFile(const char *szLocalFilename, const char *szAddAs, bool fDelete=false)
Definition: C4Record.cpp:275
bool ReadBinary(const StdBuf &Buf)
Definition: C4Record.cpp:515
#define DirectorySeparator
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:253
void Copy()
Definition: StdBuf.h:475
C4PacketBase * getPkt() const
Definition: C4PacketBase.h:255
bool StartStreaming(bool fInitial)
Definition: C4Record.cpp:320
bool Flush()
Definition: CStdFile.h:71
C4PacketType getPktType() const
Definition: C4PacketBase.h:254
void Default()
Definition: C4Packet2.cpp:230
void * getMData()
Definition: StdBuf.h:108
bool C4Group_PackDirectory(const char *szFilename)
Definition: C4Group.cpp:284
void ChangeToLocal()
C4IDPacket * firstPkt() const
Definition: C4PacketBase.h:277
#define DEBUGREC_START_FRAME
Definition: C4Include.h:31
StdIntAdapt< T > mkIntAdapt(T &rValue)
Definition: StdAdaptors.h:230
void DebugRecError(const char *szError)
Definition: C4Record.cpp:1112
uint8_t iFrm
Definition: C4Record.h:99
int iSize
Definition: TstC4NetIO.cpp:35
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:277
static bool StreamToRecord(const char *szStream, StdStrBuf *pRecord)
Definition: C4Record.cpp:1118