/* ** 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 . */ /////////////////////////////////////////////////////////////////////////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 #include // 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_bufferUsed250) 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<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 for a detailed command help.\n"; else if (!strcmp(argv[0],"add")) dbg << "add [ [ ] ]\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 \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 [ ]\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 \n\n" "Removes all active splits matching the given name pattern."; else dbg << "Unknown flat I/O command"; } else if (!strcmp(cmd,"add")) { // add [ [ ] ] __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 if (argn) { strncpy(m_copyDir,argv[0],sizeof(m_copyDir)-1); m_copyDir[sizeof(m_copyDir)-1]=0; } } else if (!strcmp(cmd,"splitadd")) { // splitadd [ ] 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<stringTypes|=1<stringTypes|=1<stringTypes|=1<stringTypes|=1<stringTypes|=1<stringTypes|=1<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;tstringTypes&(1<items << " " << cur->name << "\n"; } } else if (!strcmp(cmd,"splitremove")) { // splitremove 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); }