/* Relay -- a tool to record and play Quake2 demos Copyright (C) 2000 Conor Davis 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. Conor Davis cedavis@planetquake.com */ #include #include #include #include #include #include #include "shared.h" #include "block.h" #include "bsp.h" #include "cmd.h" #include "dc_local.h" #include "dm2.h" #include "endian.h" #include "getopt.h" #include "mem.h" #include "pak.h" #include "q2defines.h" #include "q2utils.h" #include "utils.h" PFILE *infile; bsp_t map; dm2_t dm2in; serverdata_t outsvd; int playernum; size_t startframe; size_t endframe; qboolean wrote_preframe; // things I don't want to recalculate all the time int player_cluster; block_t out; char out_buffer[MAX_MSGLEN]; byte out_active[UPDATE_BACKUP][MAX_EDICTS/8]; size_t last_frame; extern int Frame_Parse(block_t *block); void syntax() { fprintf(stderr, "Demo Converter (democonv) " __DATE__ "\n"); fprintf(stderr, "Version " RELAY_VERSION " " RELAY_RELEASE "\n"); fprintf(stderr, "Copyright (C) 2000 Conor Davis\n"); fprintf(stderr, "democonv [option ...] filename filename\n"); fprintf(stderr, "-h, --help display this help and exit\n"); fprintf(stderr, "-V, --version print version information and exit\n"); fprintf(stderr, "-b, --basedir DIR uses DIR as the quake2 directory\n"); fprintf(stderr, " (. is default)\n"); fprintf(stderr, "-p, --player PLAYER sets the player perspective to use (0-255)\n"); fprintf(stderr, " (0 is default)\n"); fprintf(stderr, "-s, --start FRAME beginning frame in source demo to record\n"); fprintf(stderr, "-e, --end FRAME end frame in source demo to record\n"); exit(1); } void version() { fprintf(stderr, "democonv " RELAY_VERSION " " RELAY_RELEASE " (" __DATE__ ")\n"); exit(0); } // // Main Program // qboolean ReadFrame() { block_t in; char in_buffer[MAX_MSGLEN]; BlockInit(&in, in_buffer, sizeof(in_buffer)); if (DM2_ReadBlock(&in, infile) < 0) Sys_Error("Error reading from demo: %s\n", strerror(errno)); if (in.writeoffset == 0xffffffff) return false; if (Frame_Parse(&in) < 0) Sys_Error("Error parsing demo\n"); return true; } int main(int argc, char *argv[]) { int i, len, option_index; block_t bspfile; char *infilename, *outfilename; char basedir[MAX_OSPATH], path[MAX_OSPATH]; PFILE *outfile; size_t start_time, elapsed_time, frames_read, frames_written; struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"player", required_argument, 0, 'p'}, {"start", required_argument, 0, 's'}, {"end", required_argument, 0, 'e'}, {"basedir", required_argument, 0, 'b'}, {NULL, 0, 0, 0} }; start_time = mstime(); DM2_Init(&dm2in); BlockInit(&out, out_buffer, sizeof(out_buffer)); // default settings strcpy(basedir, "."); playernum = 0; startframe = 0; endframe = 0; frames_read = 0; frames_written = 0; option_index = 0; for (;;) { i = getopt_long(argc, argv, "hVp:s:e:", long_options, &option_index); if (i == EOF) break; switch(i) { case 'V': version(); break; case 'b': strcpy(basedir, optarg); break; case 'e': endframe = (unsigned)atoi(optarg); break; case 'h': syntax(); break; case 'p': playernum = atoi(optarg); break; case 's': startframe = (unsigned)atoi(optarg); break; default: break; } } if (argc - optind != 2) { fprintf(stderr, "Error: must supply a source and destination filename\n"); syntax(); } infilename = argv[optind]; outfilename = argv[optind+1]; printf("%s -> %s\n", infilename, outfilename); infile = pfopen(infilename, "rb"); if (!infile) Sys_Error("Error: Unable to open file %s\n", infilename); // read pre-frame information if (DM2_ReadPreFrame(&dm2in.svd, NULL, dm2in.configstrings, &dm2in.baselines, infile) < 0) Sys_Error("Error reading pre-frame data\n"); if (dm2in.svd.isdemo == RECORD_RELAY) dm2in.maxclients = atoi(dm2in.configstrings[CS_MAXCLIENTS]); else dm2in.maxclients = 1; dm2in.players = Z_Malloc(dm2in.maxclients*sizeof(player_t)); if (playernum >= dm2in.maxclients) Sys_Error("Error: player out of range (%d >= %d)\n", playernum, dm2in.maxclients); // determine where to look for quake2 files sprintf(path, "%s/baseq2", basedir); AddPackDir(path, PACK_FILES|PACK_PACKS); if (dm2in.svd.game[0] && strcmp(dm2in.svd.game, "baseq2")) { sprintf(path, "%s/%s", basedir, dm2in.svd.game); AddPackDir(path, PACK_FILES|PACK_PACKS); } // get the map name and load the map info if (Pack_LoadFile(dm2in.configstrings[CS_MODELS+1], &bspfile) < 0) Sys_Error("Error loading bsp file %s\n", dm2in.configstrings[CS_MODELS+1]); if (ReadBSP(&map, &bspfile) < 0) Sys_Error("Error parsing bsp file %s\n", dm2in.configstrings[CS_MODELS+1]); Z_Free(bspfile.buffer); outsvd = dm2in.svd; outsvd.version = 34; outsvd.isdemo = RECORD_CLIENT; outsvd.player = playernum; outfile = pfopen(outfilename, "wb"); if (!outfile) Sys_Error("Error: Unable to write to file %s\n", outfilename); DM2_FillConfigstrings(dm2in.configstrings); // read and write frame information last_frame = BASELINES_FRAME; wrote_preframe = false; for (;;) { if (!ReadFrame()) break; frames_read++; if (startframe && dm2in.current_frame < startframe) continue; if (endframe && dm2in.current_frame > endframe) break; if (!ISBITSET(dm2in.states[dm2in.current_frame & UPDATE_MASK].connected, playernum)) continue; if (!wrote_preframe) { if (DM2_WritePreFrame(&outsvd, (relayinfo_t *)NULL, dm2in.configstrings, &dm2in.baselines, outfile) < 0) Sys_Error("Error writing pre-frame data\n"); wrote_preframe = true; } if (WriteOverflow(&out)) { fprintf(stderr, "Warning: Write overflow (%u > %u bytes)\n", out.writelen, out.size); continue; } if (DM2_WriteBlock(&out, outfile) < 0) Sys_Error("Error writing to %s: %s\n", outfilename, strerror(errno)); frames_written++; last_frame = dm2in.current_frame; BlockRewind(&out); } // write end-of-demo marker len = LittleLong(0xffffffff); if (!pfwrite(&len, 4, 1, outfile)) Sys_Error("Error writing to %s: %s\n", outfilename, strerror(errno)); pfclose(infile); pfclose(outfile); if (dm2in.players) Z_Free(dm2in.players); RemoveAllPackDirs(); Cmd_ResetArgs(); Z_FreeAll(); elapsed_time = mstime() - start_time; printf("Seconds elapsed: %.3f\n", (float)elapsed_time / 1000); printf("Frames read: %u\n", frames_read); printf("Frames written: %u\n", frames_written); printf("Avg. frames/sec: %.3f\n", (float)frames_read / elapsed_time * 1000); return 0; } void Sys_Error(char *fmt, ...) { va_list argptr; char buffer[0x1000]; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); fprintf(stderr, "%s", buffer); exit(1); } void Com_Printf(char *fmt, ...) { va_list argptr; char buffer[0x1000]; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); printf("%s", buffer); }