| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- /*
- ** Command & Conquer Generals Zero Hour(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program is free software: you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation, either version 3 of the License, or
- ** (at your option) any later version.
- **
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- /////////////////////////////////////////////////////////////////////////EA-V1
- // $File: //depot/GeneralsMD/Staging/code/Libraries/Source/debug/debug_io_flat.cpp $
- // $Author: mhoffe $
- // $Revision: #1 $
- // $DateTime: 2003/07/03 11:55:26 $
- //
- // ©2003 Electronic Arts
- //
- // Debug I/O class flat (flat or split log file)
- //////////////////////////////////////////////////////////////////////////////
- #include "_pch.h"
- #include <stdlib.h>
- #include <new> // needed for placement new prototype
- DebugIOFlat::OutputStream::OutputStream(const char *filename, unsigned maxSize):
- m_bufferUsed(0), m_nextChar(0)
- {
- m_fileName=(char *)DebugAllocMemory(strlen(filename)+1);
- strcpy(m_fileName,filename);
- m_limitedFileSize=maxSize>0;
- m_bufferSize=m_limitedFileSize?maxSize:0x10000;
- m_buffer=(char *)DebugAllocMemory(m_bufferSize);
- if (!m_limitedFileSize)
- m_fileHandle=CreateFile(m_fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
- FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
- NULL);
- }
- DebugIOFlat::OutputStream::~OutputStream()
- {
- }
- DebugIOFlat::OutputStream *DebugIOFlat::OutputStream::Create(const char *filename, unsigned maxSize)
- {
- return new (DebugAllocMemory(sizeof(OutputStream))) OutputStream(filename,maxSize);
- }
- void DebugIOFlat::OutputStream::Delete(const char *path)
- {
- Flush();
- if (!m_limitedFileSize)
- CloseHandle(m_fileHandle);
- if (path&&*path)
- {
- // copy file to given path
- int run=-1;
- char *ext=strrchr(m_fileName,'.');
- if (!ext)
- ext=m_fileName+strlen(m_fileName);
- char *fileNameOnly=strrchr(m_fileName,'\\');
- fileNameOnly=fileNameOnly?fileNameOnly+1:m_fileName;
- unsigned pathLen=strlen(path);
- for (;;)
- {
- // absolute path?
- char help[512];
- if (path[0]&&(path[1]==':'||(path[0]=='\\'&&path[1]=='\\')))
- {
- strcpy(help,path);
- strcpy(help+pathLen,fileNameOnly);
- help[ext-fileNameOnly+pathLen]=0;
- }
- else
- {
- // no, relative path given
- strcpy(help,m_fileName);
- strcpy(help+(fileNameOnly-m_fileName),path);
- strcpy(help+(fileNameOnly-m_fileName)+pathLen,fileNameOnly);
- help[ext-fileNameOnly+pathLen+(fileNameOnly-m_fileName)]=0;
- }
- if (++run)
- wsprintf(help+strlen(help),"(%i)%s",run,ext);
- else
- strcat(help,ext);
- if (CopyFile(m_fileName,help,TRUE))
- break;
- if (GetLastError()!=ERROR_FILE_EXISTS)
- break;
- }
- }
- DebugFreeMemory(m_buffer);
- DebugFreeMemory(m_fileName);
- this->~OutputStream();
- DebugFreeMemory(this);
- }
- void DebugIOFlat::OutputStream::Write(const char *src)
- {
- if (!src)
- {
- // flush request, flush only if unlimited file size
- if (!m_limitedFileSize)
- Flush();
- }
- else
- {
- unsigned len=strlen(src);
- while (len>m_bufferSize)
- {
- InternalWrite(src,m_bufferSize);
- src+=m_bufferSize;
- len-=m_bufferSize;
- }
- InternalWrite(src,len);
- }
- }
- void DebugIOFlat::OutputStream::InternalWrite(const char *src, unsigned len)
- {
- __ASSERT(len<=m_bufferSize);
- // unlimited log file length?
- if (!m_limitedFileSize)
- {
- if (m_bufferUsed+len>m_bufferSize)
- Flush();
- memcpy(m_buffer+m_bufferUsed,src,len);
- m_bufferUsed+=len;
- }
- else
- {
- // just write to ring buffer
- if ((m_bufferUsed+=len)>m_bufferSize)
- m_bufferUsed=m_bufferSize;
- while (len)
- {
- unsigned toWrite;
- if (m_nextChar+len>m_bufferSize)
- toWrite=m_bufferSize-m_nextChar;
- else
- toWrite=len;
- memcpy(m_buffer+m_nextChar,src,toWrite);
- if ((m_nextChar+=toWrite)>=m_bufferSize)
- m_nextChar=0;
- src+=toWrite;
- len-=toWrite;
- }
- }
- }
- void DebugIOFlat::OutputStream::Flush(void)
- {
- if (!m_limitedFileSize)
- {
- // simple flush to file
- DWORD written;
- WriteFile(m_fileHandle,m_buffer,m_bufferUsed,&written,NULL);
- m_bufferUsed=0;
- }
- else
- {
- // create file, write ring buffer
- m_fileHandle=CreateFile(m_fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
- FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
- NULL);
- DWORD written;
- if (m_bufferUsed<m_bufferSize)
- WriteFile(m_fileHandle,m_buffer,m_bufferUsed,&written,NULL);
- else
- {
- WriteFile(m_fileHandle,m_buffer+m_nextChar,m_bufferUsed-m_nextChar,&written,NULL);
- WriteFile(m_fileHandle,m_buffer,m_nextChar,&written,NULL);
- }
- CloseHandle(m_fileHandle);
- }
- }
- //////////////////////////////////////////////////////////////////////////////
- void DebugIOFlat::ExpandMagic(const char *src, const char *splitName, char *buf)
- {
- // barf if too long
- if (strlen(src)>250)
- src="*eMN";
- // non-magic name?
- if (*src!='*')
- {
- // just return input name
- if (splitName)
- {
- // must jam in split name before extension
- const char *p=strrchr(src,'.');
- if (!p)
- p=src+strlen(src);
- strncpy(buf,src,p-src);
- buf[p-src]='-';
- strcpy(buf+(p-src)+1,splitName);
- strcat(buf,p);
- }
- else
- strcpy(buf,src);
- return;
- }
- // translate magic name
- src++;
- char *dst=buf;
- while (*src)
- {
- if (dst-buf>250)
- break;
- if (*src>='A'&&*src<='Z'&&(*src!='N'||splitName))
- *dst++='-';
- char help[256];
- DWORD size=sizeof(help);
- *help=0;
- switch(*src++)
- {
- case 'e':
- case 'E':
- GetModuleFileName(NULL,help,sizeof(help));
- break;
- case 'm':
- case 'M':
- GetComputerName(help,&size);
- break;
- case 'u':
- case 'U':
- GetUserName(help,&size);
- break;
- case 't':
- case 'T':
- {
- SYSTEMTIME systime;
- GetLocalTime(&systime);
- wsprintf(help,"%04i%02i%02i-%02i%02i-%02i",
- systime.wYear,systime.wMonth,systime.wDay,
- systime.wHour,systime.wMinute,systime.wSecond);
- }
- break;
- case 'n':
- case 'N':
- if (splitName&&strlen(splitName)<250)
- strcpy(help,splitName);
- break;
- default:
- *dst++=src[-1];
- }
- unsigned len=strlen(help);
- if (dst-buf+len>250)
- break;
- strcpy(dst,help);
- dst+=len;
- }
- strcpy(dst,".log");
- }
- DebugIOFlat::DebugIOFlat(void):
- m_firstStream(NULL), m_firstSplit(NULL),
- m_lastStreamPtr(&m_firstStream), m_lastSplitPtr(&m_firstSplit)
- {
- *m_copyDir=0;
- }
- DebugIOFlat::~DebugIOFlat()
- {
- for (SplitListEntry *cur=m_firstSplit;cur;)
- {
- SplitListEntry *kill=cur;
- cur=cur->next;
- DebugFreeMemory(kill);
- }
- m_firstSplit=NULL;
- for (StreamListEntry *stream=m_firstStream;stream;)
- {
- StreamListEntry *kill=stream;
- stream=stream->next;
- kill->stream->Delete(m_copyDir);
- DebugFreeMemory(kill);
- }
- }
- void DebugIOFlat::Write(StringType type, const char *src, const char *str)
- {
- for (SplitListEntry *cur=m_firstSplit;cur;cur=cur->next)
- {
- if (!(cur->stringTypes&(1<<type)))
- continue;
- if (src&&*src&&!Debug::SimpleMatch(src,cur->items))
- continue;
- cur->stream->Write(str);
- break;
- }
- if (!cur)
- m_firstStream->stream->Write(str);
- }
- void DebugIOFlat::EmergencyFlush(void)
- {
- for (StreamListEntry *cur=m_firstStream;cur;cur=cur->next)
- cur->stream->Flush();
- }
- void DebugIOFlat::Execute(class Debug& dbg, const char *cmd, bool structuredCmd,
- unsigned argn, const char * const * argv)
- {
- if (!cmd||!strcmp(cmd,"help"))
- {
- if (!argn)
- dbg << "flat I/O help:\n"
- "The following I/O commands are defined:\n"
- " add, copy, splitadd, splitview, splitremove\n"
- "Type in debug.io flat help <cmd> for a detailed command help.\n";
- else if (!strcmp(argv[0],"add"))
- dbg <<
- "add [ <filename> [ <size in kb> ] ]\n\n"
- "Create flat file I/O (optionally specifying file name and file size).\n"
- "If a filename is specified all output is written to that file. Otherwise\n"
- "the magic filename '*eMN' is automatically used. Any existing files with\n"
- "that name are overwritten.\n"
- "\n"
- "Instead of a real file name a 'magic' file name can be used by starting\n"
- "the file name with a '*' followed by any number of special characters:\n"
- "- 'e': inserts EXE name\n"
- "- 'm': inserts machine name\n"
- "- 'u': inserts username\n"
- "- 't': inserts timestamp\n"
- "- 'n': inserts split name (empty if main log file)\n"
- "- '-': inserts '-'\n"
- "- 'E', 'M', 'U', 'T', 'N': same as above but with '-' in front \n"
- " (for 'N': empty if main log file)\n"
- ".log is automatically appended if using magic file names.\n"
- "\n"
- "If a size is specified then all data is internally written to a fixed \n"
- "size memory based ring buffer. This data is flushed out once the \n"
- "program exits. If no size is given then the size of the log file is not \n"
- "limited and any log data is written out immediately.\n";
- else if (!strcmp(argv[0],"copy"))
- dbg <<
- "copy <directory>\n\n"
- "Copies generated log file(s) into the given directory if the program\n"
- "exists or crashes. If there is already a log file with the same\n"
- "name a unique number is appended to the current log files' name.\n";
- else if (!strcmp(argv[0],"splitadd"))
- dbg <<
- "splitadd <types> <filter> <name> [ <size in kb> ]\n\n"
- "Splits off part of the log data. Multiple splits can be defined. They \n"
- "are written out to the first matching split file.\n"
- "\n"
- "'types' defines one or more string types which should be split off:\n"
- "- a: asserts\n"
- "- c: checks\n"
- "- l: logs\n"
- "- h: crash\n"
- "- x: exceptions\n"
- "- r: replies from commands\n"
- "- o: other messages \n"
- "\n"
- "Next a filter is specified that determines which items are to be \n"
- "filtered (only for string types a, c and l). Items can be listed with\n"
- "then 'list' command. The filter is exactly specified as in that command.\n"
- "\n"
- "The third parameter defines a name for this split. If there is \n"
- "already a split with the same name then both will write to the same \n"
- "destination file.\n"
- "\n"
- "Note: If splits are used and the filename given for 'add' is static \n"
- "or does not contain 'n' or 'N' then the split name is automatically \n"
- "appended to the log file name (before the extension).\n"
- "\n"
- "If a size is specified then all data is internally written to a \n"
- "fixed size memory based ring buffer. This data is flushed out once \n"
- "the program exits.\n"
- "\n"
- "If no size is given then the size of the log file is not limited and \n"
- "any log data is written out immediately.\n";
- else if (!strcmp(argv[0],"splitview"))
- dbg << "splitview\n\n"
- "Shows all existing splits in the order they are evaluated.";
- else if (!strcmp(argv[0],"splitremove"))
- dbg << "splitremove <namepattern>\n\n"
- "Removes all active splits matching the given name pattern.";
- else
- dbg << "Unknown flat I/O command";
- }
- else if (!strcmp(cmd,"add"))
- {
- // add [ <filename> [ <size in kb> ] ]
- __ASSERT(m_firstStream==NULL);
- strcpy(m_baseFilename,argn?argv[0]:"*eMN");
- char fn[256];
- ExpandMagic(m_baseFilename,NULL,fn);
- m_firstStream=(StreamListEntry *)DebugAllocMemory(sizeof(StreamListEntry));
- m_firstStream->next=NULL;
- m_firstStream->stream=OutputStream::Create(fn,argn>1?atoi(argv[1])*1024:0);
- m_lastStreamPtr=&m_firstStream->next;
- }
- else if (!strcmp(cmd,"copy"))
- {
- // copy <directory>
- if (argn)
- {
- strncpy(m_copyDir,argv[0],sizeof(m_copyDir)-1);
- m_copyDir[sizeof(m_copyDir)-1]=0;
- }
- }
- else if (!strcmp(cmd,"splitadd"))
- {
- // splitadd <types> <filter> <name> [ <size in kb> ]
- if (argn>=3)
- {
- // add entry
- SplitListEntry *cur=(SplitListEntry *)DebugAllocMemory(sizeof(SplitListEntry));
- cur->next=*m_lastSplitPtr;
- m_lastSplitPtr=&cur->next;
- if (!m_firstSplit)
- m_firstSplit=cur;
- cur->stringTypes=0;
- for (const char *p=argv[0];*p;++p)
- {
- switch(*p)
- {
- case 'a': cur->stringTypes|=1<<Assert; break;
- case 'c': cur->stringTypes|=1<<Check; break;
- case 'l': cur->stringTypes|=1<<Log; break;
- case 'h': cur->stringTypes|=1<<Crash; break;
- case 'x': cur->stringTypes|=1<<Exception; break;
- case 'r': cur->stringTypes|=1<<CmdReply; break;
- case 'o': cur->stringTypes|=1<<Other; break;
- }
- }
- if (!cur->stringTypes)
- cur->stringTypes=0xffffffff;
- strncpy(cur->items,argv[1],sizeof(cur->items)-1);
- cur->items[sizeof(cur->items)-1]=0;
- strncpy(cur->name,argv[2],sizeof(cur->name)-1);
- cur->name[sizeof(cur->name)-1]=0;
- // create our filename, search for stream with same filename
- char fn[256];
- ExpandMagic(m_baseFilename,cur->name,fn);
- for (StreamListEntry *stream=m_firstStream;stream;stream=stream->next)
- if (!strcmp(stream->stream->GetFilename(),fn))
- break;
- if (!stream)
- {
- // must create new stream
- stream=(StreamListEntry *)DebugAllocMemory(sizeof(StreamListEntry));
- stream->next=NULL;
- *m_lastStreamPtr=stream;
- m_lastStreamPtr=&stream->next;
- stream->stream=OutputStream::Create(fn,argn>3?atoi(argv[3])*1024:0);
- }
- cur->stream=stream->stream;
- }
- }
- else if (!strcmp(cmd,"splitview"))
- {
- // splitview
- for (SplitListEntry *cur=m_firstSplit;cur;cur=cur->next)
- {
- for (StringType t=Assert;t<MAX;t=(StringType)(t+1))
- {
- if (t==StructuredCmdReply||!(cur->stringTypes&(1<<t)))
- continue;
- switch(t)
- {
- case Assert: dbg << "a"; break;
- case Check: dbg << "c"; break;
- case Log: dbg << "l"; break;
- case Crash: dbg << "h"; break;
- case Exception: dbg << "x"; break;
- case CmdReply: dbg << "r"; break;
- case Other: dbg << "o"; break;
- }
- }
- dbg << " " << cur->items << " " << cur->name << "\n";
- }
- }
- else if (!strcmp(cmd,"splitremove"))
- {
- // splitremove <namepattern>
- const char *pattern=argn<1?"*":argv[0];
- for (SplitListEntry **entryPtr=&m_firstSplit;*entryPtr;)
- {
- if ( Debug::SimpleMatch((*entryPtr)->name,pattern) )
- {
- // remove this entry
- SplitListEntry *cur=*entryPtr;
- *entryPtr=cur->next;
- DebugFreeMemory(cur);
- }
- else
- entryPtr=&((*entryPtr)->next);
- }
-
- // must fixup m_lastSplitPtr now
- if (m_firstSplit)
- {
- for (SplitListEntry *cur=m_firstSplit;cur->next;cur=cur->next);
- m_firstSplit=cur;
- }
- else
- m_firstSplit=NULL;
- }
- }
- DebugIOInterface *DebugIOFlat::Create(void)
- {
- return new (DebugAllocMemory(sizeof(DebugIOFlat))) DebugIOFlat();
- }
- void DebugIOFlat::Delete(void)
- {
- this->~DebugIOFlat();
- DebugFreeMemory(this);
- }
|