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