//===========================================================================// // matchlog.cpp -- per-peer MP forensic combat log (PORT tooling, 2026-07-22)// //---------------------------------------------------------------------------// // See matchlog.hpp for the contract + event catalogue. This TU only owns // the file + the line writer; the event call sites live at the authoritative // combat hooks (mech.cpp DMG, mechweap.cpp FIRE, mech4.cpp DEATH/PROJ/ // SPLASH/RAM, btplayer.cpp VEHICLE/PLAYER_DEAD/SCORE, mechdmg.cpp CRIT) and // two engine seams (APP.cpp MISSION, L4NET.CPP PEER_UP/DOWN via extern). //===========================================================================// #include #include // Application/application (the st= line prefix) #include #include #include #include int BTMatchHostOf(const EntityID &id) { EntityID copy(id); // GetHostID() is non-const return (int)copy.GetHostID(); } int BTMatchLocalOf(const EntityID &id) { return (int)id; // operator int() const == localID } static FILE *s_matchLog = 0; static int s_matchState = -1; // -1 unresolved, 0 off, 1 armed static char s_matchPath[160] = ""; const char * BTMatchLogPath() { return s_matchPath; } void BTMatchLogClose() { if (s_matchLog != 0) { fflush(s_matchLog); fclose(s_matchLog); s_matchLog = 0; s_matchState = 0; // further events: no-op } } int BTMatchLogActive() { if (s_matchState >= 0) return s_matchState; // // Resolve the gate once. BT_MATCHLOG overrides; the default arms for // any "-net" launch (every MP path) and stays silent for solo. // int on; const char *e = getenv("BT_MATCHLOG"); if (e != 0) { on = (atoi(e) != 0); } else { const char *cmd = GetCommandLineA(); on = (cmd != 0 && strstr(cmd, "-net") != 0); } if (on) { SYSTEMTIME now; GetLocalTime(&now); sprintf(s_matchPath, "matchlog_%04u%02u%02u_%02u%02u%02u_%lu.txt", (unsigned)now.wYear, (unsigned)now.wMonth, (unsigned)now.wDay, (unsigned)now.wHour, (unsigned)now.wMinute, (unsigned)now.wSecond, (unsigned long)GetCurrentProcessId()); s_matchLog = fopen(s_matchPath, "w"); if (s_matchLog == 0) s_matchPath[0] = '\0'; } s_matchState = (s_matchLog != 0) ? 1 : 0; if (s_matchState) { char machine[MAX_COMPUTERNAME_LENGTH + 1] = "?"; DWORD machine_length = sizeof(machine); GetComputerNameA(machine, &machine_length); const char *self = getenv("BT_SELF"); const char *relay = getenv("BT_RELAY"); SYSTEMTIME now; GetLocalTime(&now); BTMatchLog("HDR", "ver=\"%s\" date=%04u-%02u-%02u pid=%lu machine=\"%s\" " "self=\"%s\" relay=\"%s\" cmd=\"%s\"", BT_VERSION_FULL, (unsigned)now.wYear, (unsigned)now.wMonth, (unsigned)now.wDay, (unsigned long)GetCurrentProcessId(), machine, self != 0 ? self : "", relay != 0 ? relay : "", GetCommandLineA() != 0 ? GetCommandLineA() : ""); } return s_matchState; } void BTMatchLog(const char *tag, const char *format, ...) { if (!BTMatchLogActive()) return; char body[640]; va_list args; va_start(args, format); _vsnprintf(body, sizeof(body) - 1, format, args); body[sizeof(body) - 1] = 0; va_end(args); SYSTEMTIME now; GetLocalTime(&now); // The app state rides every line (WaitingForEgg .. RunningMission ..) // so the tool can window events to the live mission without a separate // state-transition hook. int app_state = (application != 0) ? (int)application->GetApplicationState() : -1; fprintf(s_matchLog, "%s t=%lu w=%02u:%02u:%02u.%03u st=%d %s\n", tag, (unsigned long)GetTickCount(), (unsigned)now.wHour, (unsigned)now.wMinute, (unsigned)now.wSecond, (unsigned)now.wMilliseconds, app_state, body); fflush(s_matchLog); }