Richard Boegli's CnC_Generals_Zero_Hour Fork WIP
This is documentation of Richard Boegil's Zero Hour Fork
 
Loading...
Searching...
No Matches
debug_io_flat.cpp
Go to the documentation of this file.
1/*
2** Command & Conquer Generals Zero Hour(tm)
3** Copyright 2025 Electronic Arts Inc.
4**
5** This program is free software: you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation, either version 3 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/debug/debug_io_flat.cpp $
21// $Author: mhoffe $
22// $Revision: #1 $
23// $DateTime: 2003/07/03 11:55:26 $
24//
25// ©2003 Electronic Arts
26//
27// Debug I/O class flat (flat or split log file)
29#include "_pch.h"
30#include <stdlib.h>
31#include <new> // needed for placement new prototype
32
33DebugIOFlat::OutputStream::OutputStream(const char *filename, unsigned maxSize):
34 m_bufferUsed(0), m_nextChar(0)
35{
36 m_fileName=(char *)DebugAllocMemory(strlen(filename)+1);
37 strcpy(m_fileName,filename);
38
39 m_limitedFileSize=maxSize>0;
40 m_bufferSize=m_limitedFileSize?maxSize:0x10000;
41 m_buffer=(char *)DebugAllocMemory(m_bufferSize);
42
43 if (!m_limitedFileSize)
44 m_fileHandle=CreateFile(m_fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
45 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
46 NULL);
47}
48
49DebugIOFlat::OutputStream::~OutputStream()
50{
51}
52
53DebugIOFlat::OutputStream *DebugIOFlat::OutputStream::Create(const char *filename, unsigned maxSize)
54{
55 return new (DebugAllocMemory(sizeof(OutputStream))) OutputStream(filename,maxSize);
56}
57
58void DebugIOFlat::OutputStream::Delete(const char *path)
59{
60 Flush();
61 if (!m_limitedFileSize)
62 CloseHandle(m_fileHandle);
63
64 if (path&&*path)
65 {
66 // copy file to given path
67 int run=-1;
68 char *ext=strrchr(m_fileName,'.');
69 if (!ext)
70 ext=m_fileName+strlen(m_fileName);
71 char *fileNameOnly=strrchr(m_fileName,'\\');
72 fileNameOnly=fileNameOnly?fileNameOnly+1:m_fileName;
73
74 unsigned pathLen=strlen(path);
75 for (;;)
76 {
77 // absolute path?
78 char help[512];
79 if (path[0]&&(path[1]==':'||(path[0]=='\\'&&path[1]=='\\')))
80 {
81 strcpy(help,path);
82 strcpy(help+pathLen,fileNameOnly);
83 help[ext-fileNameOnly+pathLen]=0;
84 }
85 else
86 {
87 // no, relative path given
88 strcpy(help,m_fileName);
89 strcpy(help+(fileNameOnly-m_fileName),path);
90 strcpy(help+(fileNameOnly-m_fileName)+pathLen,fileNameOnly);
91 help[ext-fileNameOnly+pathLen+(fileNameOnly-m_fileName)]=0;
92 }
93 if (++run)
94 wsprintf(help+strlen(help),"(%i)%s",run,ext);
95 else
96 strcat(help,ext);
97 if (CopyFile(m_fileName,help,TRUE))
98 break;
99 if (GetLastError()!=ERROR_FILE_EXISTS)
100 break;
101 }
102 }
103
104 DebugFreeMemory(m_buffer);
105 DebugFreeMemory(m_fileName);
106
107 this->~OutputStream();
108 DebugFreeMemory(this);
109}
110
111void DebugIOFlat::OutputStream::Write(const char *src)
112{
113 if (!src)
114 {
115 // flush request, flush only if unlimited file size
116 if (!m_limitedFileSize)
117 Flush();
118 }
119 else
120 {
121 unsigned len=strlen(src);
122
123 while (len>m_bufferSize)
124 {
125 InternalWrite(src,m_bufferSize);
126 src+=m_bufferSize;
127 len-=m_bufferSize;
128 }
129 InternalWrite(src,len);
130 }
131}
132
133void DebugIOFlat::OutputStream::InternalWrite(const char *src, unsigned len)
134{
135 __ASSERT(len<=m_bufferSize);
136
137 // unlimited log file length?
138 if (!m_limitedFileSize)
139 {
140 if (m_bufferUsed+len>m_bufferSize)
141 Flush();
142 memcpy(m_buffer+m_bufferUsed,src,len);
143 m_bufferUsed+=len;
144 }
145 else
146 {
147 // just write to ring buffer
148 if ((m_bufferUsed+=len)>m_bufferSize)
149 m_bufferUsed=m_bufferSize;
150
151 while (len)
152 {
153 unsigned toWrite;
154 if (m_nextChar+len>m_bufferSize)
155 toWrite=m_bufferSize-m_nextChar;
156 else
157 toWrite=len;
158 memcpy(m_buffer+m_nextChar,src,toWrite);
159 if ((m_nextChar+=toWrite)>=m_bufferSize)
160 m_nextChar=0;
161 src+=toWrite;
162 len-=toWrite;
163 }
164 }
165}
166
167void DebugIOFlat::OutputStream::Flush(void)
168{
169 if (!m_limitedFileSize)
170 {
171 // simple flush to file
172 DWORD written;
173 WriteFile(m_fileHandle,m_buffer,m_bufferUsed,&written,NULL);
174 m_bufferUsed=0;
175 }
176 else
177 {
178 // create file, write ring buffer
179 m_fileHandle=CreateFile(m_fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
180 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
181 NULL);
182 DWORD written;
183 if (m_bufferUsed<m_bufferSize)
184 WriteFile(m_fileHandle,m_buffer,m_bufferUsed,&written,NULL);
185 else
186 {
187 WriteFile(m_fileHandle,m_buffer+m_nextChar,m_bufferUsed-m_nextChar,&written,NULL);
188 WriteFile(m_fileHandle,m_buffer,m_nextChar,&written,NULL);
189 }
190 CloseHandle(m_fileHandle);
191 }
192}
193
195
196void DebugIOFlat::ExpandMagic(const char *src, const char *splitName, char *buf)
197{
198 // barf if too long
199 if (strlen(src)>250)
200 src="*eMN";
201
202 // non-magic name?
203 if (*src!='*')
204 {
205 // just return input name
206 if (splitName)
207 {
208 // must jam in split name before extension
209 const char *p=strrchr(src,'.');
210 if (!p)
211 p=src+strlen(src);
212 strncpy(buf,src,p-src);
213 buf[p-src]='-';
214 strcpy(buf+(p-src)+1,splitName);
215 strcat(buf,p);
216 }
217 else
218 strcpy(buf,src);
219
220 return;
221 }
222
223 // translate magic name
224 src++;
225 char *dst=buf;
226 while (*src)
227 {
228 if (dst-buf>250)
229 break;
230
231 if (*src>='A'&&*src<='Z'&&(*src!='N'||splitName))
232 *dst++='-';
233
234 char help[256];
235 DWORD size=sizeof(help);
236 *help=0;
237
238 switch(*src++)
239 {
240 case 'e':
241 case 'E':
242 GetModuleFileName(NULL,help,sizeof(help));
243 break;
244 case 'm':
245 case 'M':
246 GetComputerName(help,&size);
247 break;
248 case 'u':
249 case 'U':
250 GetUserName(help,&size);
251 break;
252 case 't':
253 case 'T':
254 {
255 SYSTEMTIME systime;
256 GetLocalTime(&systime);
257
258 wsprintf(help,"%04i%02i%02i-%02i%02i-%02i",
259 systime.wYear,systime.wMonth,systime.wDay,
260 systime.wHour,systime.wMinute,systime.wSecond);
261 }
262 break;
263 case 'n':
264 case 'N':
265 if (splitName&&strlen(splitName)<250)
266 strcpy(help,splitName);
267 break;
268 default:
269 *dst++=src[-1];
270 }
271
272 unsigned len=strlen(help);
273 if (dst-buf+len>250)
274 break;
275 strcpy(dst,help);
276 dst+=len;
277 }
278 strcpy(dst,".log");
279}
280
282 m_firstStream(NULL), m_firstSplit(NULL),
283 m_lastStreamPtr(&m_firstStream), m_lastSplitPtr(&m_firstSplit)
284{
285 *m_copyDir=0;
286}
287
289{
290 for (SplitListEntry *cur=m_firstSplit;cur;)
291 {
292 SplitListEntry *kill=cur;
293 cur=cur->next;
294 DebugFreeMemory(kill);
295 }
296 m_firstSplit=NULL;
297
298 for (StreamListEntry *stream=m_firstStream;stream;)
299 {
300 StreamListEntry *kill=stream;
301 stream=stream->next;
302 kill->stream->Delete(m_copyDir);
303 DebugFreeMemory(kill);
304 }
305}
306
307void DebugIOFlat::Write(StringType type, const char *src, const char *str)
308{
309 for (SplitListEntry *cur=m_firstSplit;cur;cur=cur->next)
310 {
311 if (!(cur->stringTypes&(1<<type)))
312 continue;
313 if (src&&*src&&!Debug::SimpleMatch(src,cur->items))
314 continue;
315 cur->stream->Write(str);
316 break;
317 }
318 if (!cur)
319 m_firstStream->stream->Write(str);
320}
321
323{
324 for (StreamListEntry *cur=m_firstStream;cur;cur=cur->next)
325 cur->stream->Flush();
326}
327
328void DebugIOFlat::Execute(class Debug& dbg, const char *cmd, bool structuredCmd,
329 unsigned argn, const char * const * argv)
330{
331 if (!cmd||!strcmp(cmd,"help"))
332 {
333 if (!argn)
334 dbg << "flat I/O help:\n"
335 "The following I/O commands are defined:\n"
336 " add, copy, splitadd, splitview, splitremove\n"
337 "Type in debug.io flat help <cmd> for a detailed command help.\n";
338 else if (!strcmp(argv[0],"add"))
339 dbg <<
340 "add [ <filename> [ <size in kb> ] ]\n\n"
341 "Create flat file I/O (optionally specifying file name and file size).\n"
342 "If a filename is specified all output is written to that file. Otherwise\n"
343 "the magic filename '*eMN' is automatically used. Any existing files with\n"
344 "that name are overwritten.\n"
345 "\n"
346 "Instead of a real file name a 'magic' file name can be used by starting\n"
347 "the file name with a '*' followed by any number of special characters:\n"
348 "- 'e': inserts EXE name\n"
349 "- 'm': inserts machine name\n"
350 "- 'u': inserts username\n"
351 "- 't': inserts timestamp\n"
352 "- 'n': inserts split name (empty if main log file)\n"
353 "- '-': inserts '-'\n"
354 "- 'E', 'M', 'U', 'T', 'N': same as above but with '-' in front \n"
355 " (for 'N': empty if main log file)\n"
356 ".log is automatically appended if using magic file names.\n"
357 "\n"
358 "If a size is specified then all data is internally written to a fixed \n"
359 "size memory based ring buffer. This data is flushed out once the \n"
360 "program exits. If no size is given then the size of the log file is not \n"
361 "limited and any log data is written out immediately.\n";
362 else if (!strcmp(argv[0],"copy"))
363 dbg <<
364 "copy <directory>\n\n"
365 "Copies generated log file(s) into the given directory if the program\n"
366 "exists or crashes. If there is already a log file with the same\n"
367 "name a unique number is appended to the current log files' name.\n";
368 else if (!strcmp(argv[0],"splitadd"))
369 dbg <<
370 "splitadd <types> <filter> <name> [ <size in kb> ]\n\n"
371 "Splits off part of the log data. Multiple splits can be defined. They \n"
372 "are written out to the first matching split file.\n"
373 "\n"
374 "'types' defines one or more string types which should be split off:\n"
375 "- a: asserts\n"
376 "- c: checks\n"
377 "- l: logs\n"
378 "- h: crash\n"
379 "- x: exceptions\n"
380 "- r: replies from commands\n"
381 "- o: other messages \n"
382 "\n"
383 "Next a filter is specified that determines which items are to be \n"
384 "filtered (only for string types a, c and l). Items can be listed with\n"
385 "then 'list' command. The filter is exactly specified as in that command.\n"
386 "\n"
387 "The third parameter defines a name for this split. If there is \n"
388 "already a split with the same name then both will write to the same \n"
389 "destination file.\n"
390 "\n"
391 "Note: If splits are used and the filename given for 'add' is static \n"
392 "or does not contain 'n' or 'N' then the split name is automatically \n"
393 "appended to the log file name (before the extension).\n"
394 "\n"
395 "If a size is specified then all data is internally written to a \n"
396 "fixed size memory based ring buffer. This data is flushed out once \n"
397 "the program exits.\n"
398 "\n"
399 "If no size is given then the size of the log file is not limited and \n"
400 "any log data is written out immediately.\n";
401 else if (!strcmp(argv[0],"splitview"))
402 dbg << "splitview\n\n"
403 "Shows all existing splits in the order they are evaluated.";
404 else if (!strcmp(argv[0],"splitremove"))
405 dbg << "splitremove <namepattern>\n\n"
406 "Removes all active splits matching the given name pattern.";
407 else
408 dbg << "Unknown flat I/O command";
409 }
410 else if (!strcmp(cmd,"add"))
411 {
412 // add [ <filename> [ <size in kb> ] ]
413 __ASSERT(m_firstStream==NULL);
414
415 strcpy(m_baseFilename,argn?argv[0]:"*eMN");
416
417 char fn[256];
418 ExpandMagic(m_baseFilename,NULL,fn);
419
420 m_firstStream=(StreamListEntry *)DebugAllocMemory(sizeof(StreamListEntry));
421 m_firstStream->next=NULL;
422 m_firstStream->stream=OutputStream::Create(fn,argn>1?atoi(argv[1])*1024:0);
423 m_lastStreamPtr=&m_firstStream->next;
424 }
425 else if (!strcmp(cmd,"copy"))
426 {
427 // copy <directory>
428 if (argn)
429 {
430 strncpy(m_copyDir,argv[0],sizeof(m_copyDir)-1);
431 m_copyDir[sizeof(m_copyDir)-1]=0;
432 }
433 }
434 else if (!strcmp(cmd,"splitadd"))
435 {
436 // splitadd <types> <filter> <name> [ <size in kb> ]
437 if (argn>=3)
438 {
439 // add entry
440 SplitListEntry *cur=(SplitListEntry *)DebugAllocMemory(sizeof(SplitListEntry));
441 cur->next=*m_lastSplitPtr;
442 m_lastSplitPtr=&cur->next;
443 if (!m_firstSplit)
444 m_firstSplit=cur;
445 cur->stringTypes=0;
446 for (const char *p=argv[0];*p;++p)
447 {
448 switch(*p)
449 {
450 case 'a': cur->stringTypes|=1<<Assert; break;
451 case 'c': cur->stringTypes|=1<<Check; break;
452 case 'l': cur->stringTypes|=1<<Log; break;
453 case 'h': cur->stringTypes|=1<<Crash; break;
454 case 'x': cur->stringTypes|=1<<Exception; break;
455 case 'r': cur->stringTypes|=1<<CmdReply; break;
456 case 'o': cur->stringTypes|=1<<Other; break;
457 }
458 }
459 if (!cur->stringTypes)
460 cur->stringTypes=0xffffffff;
461
462 strncpy(cur->items,argv[1],sizeof(cur->items)-1);
463 cur->items[sizeof(cur->items)-1]=0;
464
465 strncpy(cur->name,argv[2],sizeof(cur->name)-1);
466 cur->name[sizeof(cur->name)-1]=0;
467
468 // create our filename, search for stream with same filename
469 char fn[256];
470 ExpandMagic(m_baseFilename,cur->name,fn);
471 for (StreamListEntry *stream=m_firstStream;stream;stream=stream->next)
472 if (!strcmp(stream->stream->GetFilename(),fn))
473 break;
474 if (!stream)
475 {
476 // must create new stream
477 stream=(StreamListEntry *)DebugAllocMemory(sizeof(StreamListEntry));
478 stream->next=NULL;
479 *m_lastStreamPtr=stream;
480 m_lastStreamPtr=&stream->next;
481 stream->stream=OutputStream::Create(fn,argn>3?atoi(argv[3])*1024:0);
482 }
483 cur->stream=stream->stream;
484 }
485 }
486 else if (!strcmp(cmd,"splitview"))
487 {
488 // splitview
489 for (SplitListEntry *cur=m_firstSplit;cur;cur=cur->next)
490 {
491 for (StringType t=Assert;t<MAX;t=(StringType)(t+1))
492 {
493 if (t==StructuredCmdReply||!(cur->stringTypes&(1<<t)))
494 continue;
495 switch(t)
496 {
497 case Assert: dbg << "a"; break;
498 case Check: dbg << "c"; break;
499 case Log: dbg << "l"; break;
500 case Crash: dbg << "h"; break;
501 case Exception: dbg << "x"; break;
502 case CmdReply: dbg << "r"; break;
503 case Other: dbg << "o"; break;
504 }
505 }
506 dbg << " " << cur->items << " " << cur->name << "\n";
507 }
508 }
509 else if (!strcmp(cmd,"splitremove"))
510 {
511 // splitremove <namepattern>
512 const char *pattern=argn<1?"*":argv[0];
513 for (SplitListEntry **entryPtr=&m_firstSplit;*entryPtr;)
514 {
515 if ( Debug::SimpleMatch((*entryPtr)->name,pattern) )
516 {
517 // remove this entry
518 SplitListEntry *cur=*entryPtr;
519 *entryPtr=cur->next;
520 DebugFreeMemory(cur);
521 }
522 else
523 entryPtr=&((*entryPtr)->next);
524 }
525
526 // must fixup m_lastSplitPtr now
527 if (m_firstSplit)
528 {
529 for (SplitListEntry *cur=m_firstSplit;cur->next;cur=cur->next);
530 m_firstSplit=cur;
531 }
532 else
533 m_firstSplit=NULL;
534 }
535}
536
537DebugIOInterface *DebugIOFlat::Create(void)
538{
539 return new (DebugAllocMemory(sizeof(DebugIOFlat))) DebugIOFlat();
540}
541
543{
544 this->~DebugIOFlat();
545 DebugFreeMemory(this);
546}
#define NULL
Definition BaseType.h:92
#define TRUE
Definition BaseType.h:109
unsigned long DWORD
Definition bittype.h:57
Debug module main class (singleton).
Definition debug_debug.h:45
static bool SimpleMatch(const char *str, const char *pattern)
virtual void Delete(void)
Destroys the current I/O class instance.
virtual void EmergencyFlush(void)
Emergency shutdown function.
static DebugIOInterface * Create(void)
virtual void Write(StringType type, const char *src, const char *str)
Write out some characters differentiated by the log string type.
virtual ~DebugIOFlat()
virtual void Execute(class Debug &dbg, const char *cmd, bool structuredCmd, unsigned argn, const char *const *argv)
I/O class specific command.
StringType
List of possible log string types.
Definition debug_io.h:67
@ Other
some other message
Definition debug_io.h:90
@ Check
DCHECK etc.
Definition debug_io.h:72
@ CmdReply
Regular command reply.
Definition debug_io.h:84
@ Crash
DCRASH etc.
Definition debug_io.h:78
@ Exception
Exception.
Definition debug_io.h:81
@ Log
DLOG etc.
Definition debug_io.h:75
@ Assert
DASSERT etc.
Definition debug_io.h:69
@ StructuredCmdReply
Structured command reply, see Structured command reply.
Definition debug_io.h:87
#define __ASSERT(x)
Definition internal.h:43
void * DebugAllocMemory(unsigned numBytes)
void DebugFreeMemory(void *ptr)