/* 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. $Id: main.c 643 2007-07-20 13:09:29Z d3urk $ */ #include "defs.h" int memsize, membase; int fps; sizebuf_t net_message; byte net_message_buffer[MAX_UDP_PACKET]; demo_t demo; source_t *from = NULL; source_t *sources = NULL; world_state_t world; static_world_state_t sworld; lightstyle_t lightstyle[MAX_LIGHTSTYLES]; entity_state_t baselines[MAX_EDICTS]; sizebuf_t stats_msg; byte stats_buf[MAX_MSGLEN]; char currentDir[1024]; // MAX_OSPATH char qizmoDir[MAX_OSPATH]; char outputDir[MAX_OSPATH]; char sourceName[MAX_SOURCES][MAX_OSPATH]; #ifdef _WIN32 HANDLE ConsoleInHndl, ConsoleOutHndl; #endif void QWDToolsMsg(void); /* ================ Sys_Error ================ */ void Sys_fclose(FILE **hndl) { if (*hndl == NULL) return; if (*hndl == stdin) return; if (*hndl == stdout) return; fclose(*hndl); *hndl = NULL; } void Sys_Exit (int i) { #ifdef _WIN32 DWORD r; struct _INPUT_RECORD in; #endif int j; Dem_Stop(NULL); for (j = 0; j < sworld.fromcount; j++) Sys_fclose(&sworld.from[j].file); Sys_fclose(&sworld.demo.file); Sys_fclose(&sworld.debug.file); Sys_fclose(&sworld.log.file); Sys_fclose(&sworld.analyse.file); FreeFileList(sworld.filelist); Q_free(sources); #ifdef _WIN32 if ( sworld.options & O_WAITFORKBHIT) { if (i != 2) SetConsoleTitle(QWDTOOLS_NAME " Done"); Sys_Printf("\nPress any key to continue\n"); do ReadConsoleInput( ConsoleInHndl, &in, 1, &r); while(in.EventType != KEY_EVENT); } #endif exit(i); } void Sys_mkdir (const char *path) { #ifdef _WIN32 _mkdir (path); #else mkdir (path, 0777); #endif } void Sys_Error (char *error, ...) { va_list argptr; char text[1024]; va_start (argptr,error); vsnprintf (text, sizeof(text), error,argptr); va_end (argptr); if (sworld.from->file != NULL && sworld.from->file != stdin) { Sys_Printf ("ERROR: %s, at:%d\n", text, ftell(sworld.from->file)); } else { Sys_Printf ("ERROR: %s\n", text); } Sys_Exit (1); } /* ================ Sys_Printf ================ */ void Sys_Printf (char *fmt, ...) { va_list argptr; char buf[1024]; #ifdef _WIN32 DWORD count; #endif // stdout can be redirected to a file or other process if ( #ifdef _WIN32 ConsoleOutHndl != NULL && #endif sworld.options & O_STDOUT) { va_start (argptr,fmt); vsnprintf(buf, sizeof(buf), fmt, argptr); va_end (argptr); #ifdef _WIN32 WriteFile(ConsoleOutHndl, buf, strlen(buf), &count, NULL); #else fprintf(stderr, "%s", buf); #endif return; } va_start (argptr,fmt); vprintf (fmt,argptr); va_end (argptr); } /* ================= ConnectionlessPacket Responses to broadcasts, etc ================= */ extern int msg_startcount; /* ================= Netchan_Process called when the current net_message is from remote_address modifies net_message so that it points to the packet payload ================= */ qbool Netchan_Process (netchan_t *chan) { unsigned sequence, sequence_ack; unsigned reliable_ack, reliable_message; // get sequence numbers MSG_BeginReading (); sequence = MSG_ReadLong (); sequence_ack = MSG_ReadLong (); // read the qport if we are a server reliable_message = sequence >> 31; reliable_ack = sequence_ack >> 31; sequence &= ~(1<<31); sequence_ack &= ~(1<<31); // // discard stale or duplicated packets // if (sequence <= (unsigned)chan->incoming_sequence) { return false; } // // if the current outgoing reliable message has been acknowledged // clear the buffer to make way for the next // if (reliable_ack == (unsigned)chan->reliable_sequence) chan->reliable_length = 0; // it has been received // // if this message contains a reliable message, bump incoming_reliable_sequence // chan->incoming_sequence = sequence; chan->incoming_acknowledged = sequence_ack; chan->incoming_reliable_acknowledged = reliable_ack; if (reliable_message) chan->incoming_reliable_sequence ^= 1; return true; } void ConnectionlessPacket (void) { int c; MSG_BeginReading (); MSG_ReadLong (); // skip the -1 c = MSG_ReadByte (); if (c == S2C_CONNECTION) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "connected\n"); from->netchan.incoming_sequence = 0; from->netchan.incoming_acknowledged= 0; from->netchan.incoming_acknowledged = 0; from->netchan.incoming_reliable_acknowledged = 0; return; } // remote command from gui front end if (c == A2C_CLIENT_COMMAND) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "A2C\n"); MSG_ReadString (); MSG_ReadString (); return; } // print command from somewhere if (c == A2C_PRINT) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "print\n"); MSG_ReadString (); return; } // ping from somewhere if (c == A2A_PING) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "ping\n"); return; } if (c == S2C_CHALLENGE) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "challenge\n"); MSG_ReadString (); return; } if (c == svc_disconnect) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "disconnect\n"); Dem_Stop(from); return; } } void Dem_Stop(source_t *s) { int i; int run; if (!sources) return; if (sworld.options & O_QWDSYNC) { if (s != NULL) s->running = 0; return; } run = world.running; if (s == NULL) for (i = 0; i < sworld.fromcount; i++) { StopQWZ(sources + i); world.running -= sources[i].running; sources[i].running = 0; } else { StopQWZ(s); world.running -= s->running; s->running = 0; } if (msgbuf) { msgbuf->cursize = 0; msgbuf->bufsize = 0; msgbuf->h = NULL; } if (!run || world.running) return; // last demo closed, say bye QWDToolsMsg(); MVDWrite_Begin(dem_all, 0, 2+strlen("EndOfDemo")); MSG_WriteByte(msgbuf, svc_disconnect); MSG_WriteString (msgbuf, "EndOfDemo"); } /* ==================== GetDemoMessage ==================== */ qbool GetDemoMessage (void) { int r, i, j, num; float f, demotime; byte newtime; byte c; usercmd_t *pcmd; num = from - sources; nextdemomessage: // read the time from the packet newtime = 0; if (!from->lasttime) { if (from->format == mvd) { r = fread(&newtime, sizeof(newtime), 1, sworld.from[num].file); from->prevtime += newtime; demotime = from->basetime + from->prevtime*0.001*from->ratio; } else { r = fread(&demotime, sizeof(demotime), 1, sworld.from[num].file); demotime = LittleFloat(demotime); } if (r != 1) { Dem_Stop(from); return 0; } } else { demotime = from->lasttime; newtime = from->prevnewtime; from->lasttime = 0; } // decide if it is time to grab the next message if (from->lastframe < 0) from->lastframe = demotime; else if (demotime > from->lastframe + 0.001) { if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "signonloaded %f, %f, %d\n", demotime, from->lastframe, newtime); from->lastframe = demotime; from->lasttime = demotime; from->prevnewtime = newtime; // signon datagram is for sure loaded now from->signonloaded = true; return 0; // already read this frame's message } if (demotime - from->time > 0.0001 && from->format == mvd) { from->netchan.incoming_sequence++; from->netchan.incoming_acknowledged++; from->frames[from->netchan.incoming_acknowledged&UPDATE_MASK].num_projectiles = 0; if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "sequence:%d\n", from->netchan.incoming_sequence); } from->time = demotime; // warp //from->prevtime = demotime; if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "msec:%d %ld\n", newtime, ftell(sworld.from[num].file)); // get the msg type fread (&c, sizeof(c), 1, sworld.from[num].file); switch (c&7) { case dem_cmd : // user sent input i = from->netchan.outgoing_sequence & UPDATE_MASK; pcmd = &from->frames[i].cmd; r = fread (pcmd, sizeof(*pcmd), 1, sworld.from[num].file); if (r != 1) { Dem_Stop(from); return 0; } // byte order stuff for (j = 0; j < 3; j++) { pcmd->angles[j] = LittleFloat(pcmd->angles[j]); if (pcmd->angles[j] > 180) pcmd->angles[j] -= 360; } pcmd->forwardmove = LittleShort(pcmd->forwardmove); pcmd->sidemove = LittleShort(pcmd->sidemove); pcmd->upmove = LittleShort(pcmd->upmove); from->frames[i].senttime = demotime; from->frames[i].receivedtime = -1; // we haven't gotten a reply yet from->netchan.outgoing_sequence++; from->frames[i].playerstate[from->playernum].command = *pcmd; for (j=0 ; j<3 ; j++) { r = fread (&f, 4, 1, sworld.from[num].file); from->frames[from->netchan.incoming_sequence&UPDATE_MASK].playerstate[from->playernum].command.angles[j] = LittleFloat (f); } break; case dem_read: readit: // get the next message fread (&net_message.cursize, 4, 1, sworld.from[num].file); net_message.cursize = LittleLong (net_message.cursize); if (net_message.cursize > MAX_UDP_PACKET) { Sys_Printf ("ERROR: Demo message > MAX_UDP_PACKET (%d)\n", net_message.cursize); Dem_Stop(from); return 0; } r = fread (net_message.data, net_message.cursize, 1, sworld.from[num].file); if (r != 1) { Dem_Stop(from); return 0; } break; case dem_set : if (sworld.options & O_DEBUG) fprintf(sworld.debug.file, "dem_set\n"); fread (&i, 4, 1, sworld.from[num].file); from->netchan.outgoing_sequence = LittleLong(i); fread (&i, 4, 1, sworld.from[num].file); from->netchan.incoming_sequence = LittleLong(i); if (from->format == mvd) { from->netchan.incoming_acknowledged = from->netchan.incoming_sequence; goto nextdemomessage; } break; case dem_multiple: r = fread (&i, 4, 1, sworld.from[num].file); if (r != 1) { Dem_Stop(from); return 0; } from->to = LittleLong(i); from->type = dem_multiple; goto readit; case dem_single: from->to = c>>3; from->type = dem_single; goto readit; case dem_stats: from->to = c>>3; from->type = dem_stats; goto readit; case dem_all: from->to = 0; from->type = dem_all; goto readit; default : Dem_Stop(from); return 0; } return 1; } int To(void); void CheckSpectator (void) { player_state_t *state, *self; int i,j; if (!from->spectator) return; self = &from->frames[from->netchan.incoming_sequence&UPDATE_MASK].playerstate[from->playernum]; for (i = 0, state = from->frames[from->netchan.incoming_sequence&UPDATE_MASK].playerstate; i < MAX_CLIENTS; i++, state++) { if (state->messagenum != from->parsecount) continue; if (i == from->playernum) continue; for (j = 0; j < 3; j++) if (abs ((int)(state->command.angles[j] - self->command.angles[j])) > 0.0) { break; } if (j != 3) continue; break; } if (i == MAX_CLIENTS) { from->spec_track = -1; } else from->spec_track = i; } void ReadPackets (void) { // if it's not a time to read packet from this demofile, return if (world.time && from->worldtime > world.time) { return; } if (!from->running) return; if (sworld.options & O_QWDSYNC) SZ_Clear(msgbuf); while (GetDemoMessage()) { // // remote command packet // if (*(int *)net_message.data == -1) { ConnectionlessPacket (); if (!from->running) { break; } continue; } if (net_message.cursize < 8 && from->format != mvd) { continue; } if (from->format == qwd) { if (!Netchan_Process(&from->netchan)) continue; // wasn't accepted for some reason } else { MSG_BeginReading (); } Dem_ParseDemoMessage (); if (sworld.options & O_QWDSYNC || !(sworld.options & (O_CONVERT | O_MARGE))) SZ_Clear(msgbuf); if (!from->running) return; } CheckSpectator(); if (stats_msg.cursize) { MVDWrite_Begin(dem_stats, To(), stats_msg.cursize); SZ_Write(msgbuf, stats_msg.data, stats_msg.cursize); SZ_Clear(&stats_msg); } if (sworld.options & O_QWDSYNC) return; /* if (world.signonloaded && from->signon_stats.cursize) { DemoWrite_Cat(&from->signon_stats); SZ_Clear(&from->signon_stats); } */ if (sworld.from->file != stdin) { int p = 0, i; long c; for (i = 0, c = 0; i < sworld.fromcount; i++) if (sources[i].running) c += ftell(sworld.from[i].file); else c += sworld.from[i].filesize; if (c - world.oldftell > 256000) { #ifdef _WIN32 if ((p = (int)100.0*c/world.demossize) != world.percentage) SetConsoleTitle(va("qwdtools %d%% (%d of %d)", p, sworld.count, sworld.sources)); #endif world.percentage = p; world.oldftell = c; } } } __inline void SetWorldTime() { static float lasttime = 0; if (lasttime > world.time) lasttime = world.time; while (lasttime <= world.time) lasttime += 1.0/sworld.fps; world.time = lasttime; } __inline void GetDemoTimes(double *mintime, double *nearest) { int i; double t; *mintime = *nearest = -1; for (i = 0; i < sworld.fromcount; i++) { if (!sources[i].running) continue; t = sources[i].worldtime; if (*mintime < 0 || *mintime > t) *mintime = t; if (*nearest < 0 || fabs(t - world.time) < fabs(*nearest - world.time)) *nearest = t; } } void AddEntities(packet_entities_t *to, frame_t *prev, frame_t *next) { packet_entities_t *prevp, *nextp, newp, worldp; double f; int i,k,j; // check if interpolations is possible if (prev->invalid || prev->parsecount > next->parsecount) prev = next; if (next->invalid) return; #if 1 if (next == prev || next->time == world.time) newp = next->packet_entities; else { f = (prev->time - next->time)/(next->time - world.time); nextp = &next->packet_entities; prevp = &prev->packet_entities; newp.num_entities = nextp->num_entities; j = 0; for (i=0; i < next->packet_entities.num_entities; i++) { while (j < prevp->num_entities && i < nextp->entities[i].number) j++; newp.entities[i] = nextp->entities[i]; if (j < prevp->num_entities && prevp->entities[j].number == nextp->entities[i].number) { for (k=0; k<3; k++) { newp.entities[i].origin[k] = prevp->entities[j].origin[k] + f*(prevp->entities[j].origin[k] - nextp->entities[i].origin[k]); newp.entities[i].angles[k] = adjustangle(nextp->entities[i].angles[k], prevp->entities[j].angles[k],1.0+f); } } else { VectorCopy(nextp->entities[i].origin, newp.entities[i].origin); VectorCopy(nextp->entities[i].angles, newp.entities[i].angles); } } } #else newp = next->packet_entities; #endif i = 0; j = 0; worldp.num_entities = 0; // scaling while (i < newp.num_entities && j < to->num_entities) if (newp.entities[i].number < to->entities[j].number) worldp.entities[worldp.num_entities++] = newp.entities[i++]; else if (newp.entities[i].number > to->entities[j].number) worldp.entities[worldp.num_entities++] = to->entities[j++]; else { worldp.entities[worldp.num_entities++] = to->entities[j++]; i++; } while (i < newp.num_entities) worldp.entities[worldp.num_entities++] = newp.entities[i++]; while (j < to->num_entities) worldp.entities[worldp.num_entities++] = to->entities[j++]; *to = worldp; } void AddNails(frame_t *to, frame_t *frame) { int num, i, j; vec3_t diff; num = to->num_projectiles; for (j = 0; j < frame->num_projectiles; j++) { for (i = 0; i < num; i++) { VectorSubtract(frame->projectiles[j].origin, to->projectiles[i].origin, diff); if (VectorLength(diff) < 64) break; } if (i != num) break; to->projectiles[to->num_projectiles++] = frame->projectiles[j]; } } void AddPlayers(frame_t *to, frame_t *frame) { int i, last; player_state_t *state, *wstate; int msec; wstate = to->playerstate; state = frame->playerstate; for (i = 0; i < MAX_CLIENTS; i++) { if (state[i].messagenum != from->parsecount) continue; if (world.players[i].lastsource) { last = world.players[i].lastsource - 1; if (last != from-sources && sources[last].running && sources[last].frames[sources[last].parsecount&UPDATE_MASK].playerstate[i].messagenum == sources[last].parsecount) continue; world.players[i].lastsource = from-sources + 1; } else world.players[i].lastsource = from-sources + 1; msec = (int)((world.time - frame->time) * 1000); if (msec > 100) msec = 100; else if (msec < -100) msec = -100; msec += state[i].command.msec; if (wstate[i].messagenum != to->parsecount) { wstate[i] = state[i]; wstate[i].msec = msec; wstate[i].messagenum = to->parsecount; } } } void Update_SignonStats(void) { int j, i, *stats, *wstats; for (from = sources; from - sources < sworld.fromcount; from++) { if (!from->running) continue; for (i = 0; i < MAX_CLIENTS; i++) { if (from->players[i].spectator) continue; if (!from->players[i].name[0]) continue; stats = from->players[i].stats; wstats = world.players[i].stats; for (j=0 ; j=0 && stats[j] <= 255) { MVDWrite_Begin(dem_stats, i, 3); MSG_WriteByte(msgbuf, svc_updatestat); MSG_WriteByte(msgbuf, j); MSG_WriteByte(msgbuf, stats[j]); } else { MVDWrite_Begin(dem_stats, i, 6); MSG_WriteByte(msgbuf, svc_updatestatlong); MSG_WriteByte(msgbuf, j); MSG_WriteLong(msgbuf, stats[j]); } } } } } qbool Synchronize (void); void MainLoop(void) { frame_t *fframe, *tframe, *pframe; float acc_time = 0, acc_ctime = 0; int acc_count = 0; int acc_count_o = 0; int num = -1; double mintime, nearest; #ifdef _WIN32 if (!(sworld.options & O_STDIN)) SetConsoleTitle(va("qwdtools 0%% (%d of %d)", sworld.count, sworld.sources)); #endif // check if demos are from the same game, and read signon datagram world.signonstats = sworld.options & O_MARGE; for (from = sources; from - sources < sworld.fromcount; from++) { DemoBuffer_Set(&from->dbuffer); ReadPackets(); } world.signonstats = false; // sync demos world.signonloaded = true; if (!Synchronize()) { Sys_Printf("couldn't synchronize demos\n"); Dem_Stop(NULL); return; } world.signonloaded = false; for (from = sources; from - sources < sworld.fromcount; from++) { if (from->running && (from->worldtime < world.time || num == -1)) { num = from - sources; demo.time = world.time = from->worldtime; } } // now write the signon datagram if (sworld.options & (O_CONVERT | O_MARGE)) { if (num == -1) { Sys_Printf("No signon datagram!\n"); Dem_Stop(NULL); return; } else { DemoBuffer_Set(&sources[num].dbuffer); if (sworld.options & O_MARGE) Update_SignonStats(); SV_MVDWriteToDisk(sources[num].dbuffer.msgbuf,0,0,0); acc_time = acc_ctime = world.time = sources[num].worldtime; world.signonloaded = true; } } // clean signon datagrams from all of the demos, they won't be needed for (from = sources; from - sources < sworld.fromcount; from++) { SZ_Clear(&from->frames[0].buf); DemoBuffer_Clear(&from->dbuffer); MVDSetMsgBuf(&from->dbuffer,&from->frames[0].buf); } // set next frame time that's wished to get SetWorldTime(); while (world.running) { if (sworld.options & O_SHUTDOWN) Sys_Exit(2); for (from = sources; from - sources < sworld.fromcount; from++) { if (!from->running) continue; DemoBuffer_Set(&from->dbuffer); // read packet from demo ReadPackets(); } // check if all demos passed world.time, find nearest one GetDemoTimes(&mintime, &nearest); // if not converting, stop here if (!(sworld.options & (O_CONVERT | O_MARGE))) { if (mintime >= world.time) { world.time = nearest; SetWorldTime(); } continue; } if (mintime >= world.time || !world.running) { // fix world.time, world.time = nearest; //interpolate entities position for this frame tframe = &world.frames[world.parsecount&UPDATE_MASK]; tframe->invalid = true; tframe->packet_entities.num_entities = 0; tframe->num_projectiles = 0; tframe->parsecount = world.parsecount; tframe->time = world.time; tframe->latency = 0; for (from = sources; from - sources < sworld.fromcount; from++) { if (!from->running) continue; if (from->oldparse == from->parsecount) { //continue; } if (from->worldtime > world.time + 1) continue; from->oldparse = from->parsecount; fframe = &from->frames[from->parsecount&UPDATE_MASK]; tframe->invalid = false; if (from->worldtime == world.time || from->parsecount == 0) { AddEntities(&tframe->packet_entities, fframe, fframe); AddNails(tframe, fframe); AddPlayers(tframe, fframe); } else { num = from->parsecount - 1; pframe = &from->frames[(num)&UPDATE_MASK]; while (pframe->time > world.time) { pframe = &from->frames[(--num)&UPDATE_MASK]; if (pframe->parsecount > fframe->parsecount) break; if (!pframe->parsecount) break; } if (pframe->parsecount <= fframe->parsecount) AddEntities(&tframe->packet_entities, pframe, fframe); else AddEntities(&tframe->packet_entities, fframe, fframe); AddNails(tframe, fframe); AddPlayers(tframe, fframe); } } if (!tframe->invalid) { SetWorldTime(); if (world.parsecount - world.lastwritten > 60) { WritePackets(30); } world.parsecount++; for (from = sources; from - sources < sworld.fromcount; from++) { MVDSetMsgBuf(&from->dbuffer,&from->frames[world.parsecount&UPDATE_MASK].buf); } } } if (!world.running) WritePackets(world.parsecount - world.lastwritten + 1); } //for (from = sources; from - sources < sworld.fromcount; from++) // Sys_Printf("sync:%f\n", from->sync); if (sworld.options & O_CONVERT) { if (acc_count) Sys_Printf(" average demo fps:%.1f (originally %.1f)\n", acc_count/(acc_ctime - acc_time), acc_count_o/(acc_ctime - acc_time)); } } void QWDToolsMsg(void) { char str[1024]; strlcpy(str, "\x1d\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1f\n" " This demo was converted\n" " from QWD by " QWDTOOLS_NAME " " QWE_VERSION"\n" "\x1d\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1f\n", sizeof(str)); MVDWrite_Begin(dem_all, 0, strlen(str) + 3); MSG_WriteByte(msgbuf, svc_print); MSG_WriteByte(msgbuf, PRINT_CHAT); MSG_WriteString(msgbuf, str); } qbool ClearWorld(void) { char *ext; extern char stdintype[32]; int i; sources = (source_t*) realloc (sources, sizeof(source_t)*sworld.fromcount); if (!sources) Sys_Error ("Not enough memory free; check disk space\n"); memset(sources, 0, sizeof(source_t)*sworld.fromcount); memset(&world, 0, sizeof(world)); memset(&demo, 0, sizeof(demo)); demo.datagram.maxsize = sizeof(demo.datagram_data); demo.datagram.data = demo.datagram_data; world.messages.data = world.buffer; world.messages.maxsize = sizeof(world.buffer); // for converting spectator demos stats_msg.maxsize = sizeof(stats_buf); stats_msg.data = stats_buf; MVDBuffer_Init(&demo.dbuffer, demo.buffer, sizeof(demo.buffer), &world.frames[0].buf); for (i = 0, from = sources; i < sworld.fromcount; i++, from++) { MVDBuffer_Init(&from->dbuffer, from->buffer, sizeof(from->buffer), &from->buf[0]); from->running = 1 << i; from->lastframe = -1; from->ratio = 1; from->signon_stats.maxsize = sizeof(from->signon_stats_buf); from->signon_stats.data = from->signon_stats_buf; world.running |= from->running; if (sworld.options & O_STDIN) { strlcpy(sworld.from[i].name, "stdin", MAX_OSPATH); ext = va(".%s", stdintype); } else ext = COM_FileExtension(sworld.from[i].name); // qwz -> qwd if (!strcasecmp(ext, "qwz")) { *(sworld.from[i].name + strlen(sworld.from[i].name) - 1) = 'd'; ext[3] = 'd'; from->qwz = true; } if (!strcasecmp(ext, "qwd")) from->format = qwd; else if (!strcasecmp(ext, "mvd")) from->format = mvd; else { Sys_Printf("Ignoring %s\n", sworld.from[i].name); return false; } } return true; } /* ================== main ================== */ int main (int argc, char **argv) { int i, j, options, count; flist_t *source; file_t *fromfiles; Argv_Init(argc, argv); CtrlH_Init(); World_Init(); // Sys_Printf( VERSION " (c) 2001 Bartlomiej Rychtarski\nhttp://qwex.n3.net/ mailto:highlander@gracz.net\n\n"); Sys_Printf(QWDTOOLS_NAME " version " QWE_VERSION " (c) 2001-2003 Bartlomiej Rychtarski\n"); Sys_Printf("Unix port by David (hexum) Balcom and VVD, 2004-2007\n"); Sys_Printf("Part of the " PROJECT_NAME " project: " PROJECT_URL "\n\n"); Tools_Init(); Load_ini(); ParseArgv(); options = sworld.options & JOB_TODO; if (sworld.options & O_FC) Sys_Printf(" -filter_chats\n"); else { if (sworld.options & O_FS) Sys_Printf(" -filter_spectalk\n"); if (sworld.options & O_FQ) Sys_Printf(" -filter_qizmotalk\n"); if (sworld.options & O_FT) Sys_Printf(" -filter_teamchats\n"); } Sys_Printf(" -fps %d\n", sworld.fps); Sys_Printf(" -msglevel %d\n\n", sworld.msglevel); if (sworld.options & O_MARGE) count = MAX_CLIENTS; else count = 1; // serve all source files in loop source = sworld.filelist; i = 0; while (1) { if (sworld.options & O_SHUTDOWN) Sys_Exit(2); fromfiles = sworld.from; sworld.fromcount = 0; // get source file names for (; sworld.fromcount < count && (i < source->count || ((i=0) < (++source)->count)); i++, fromfiles++, sworld.fromcount++) { strlcpy(fromfiles->path, source->path, sizeof(fromfiles->path)); strlcpy(fromfiles->name, source->list[i], sizeof(fromfiles->name)); } if (!sworld.fromcount) break; sworld.count += sworld.fromcount; if (sworld.fromcount == MAX_CLIENTS) Sys_Printf("Warning: too many source files\n"); // alloc memory and cleanup the world if (!ClearWorld()) { if (options & O_MARGE) { Sys_Printf("not all files could be opened\n"); break; } continue; } // try to open source files sworld.options &= ~(JOB_TODO); sworld.options |= Files_Init(options); // run program if (sworld.options & JOB_TODO) MainLoop(); for (j = 0; j < sworld.fromcount; j++) Sys_fclose(&sworld.from[j].file); Sys_fclose(&sworld.demo.file); Sys_fclose(&sworld.debug.file); Sys_fclose(&sworld.log.file); Sys_fclose(&sworld.analyse.file); // if marging no loop if (options & O_MARGE) break; } Sys_Printf("\nDone.\n"); Sys_Exit(0); return false; // to happy compiler }