demo-reader 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/README.rdoc +1 -1
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/demo-reader.gemspec +30 -7
- data/ext/dm68/README.txt +14 -0
- data/ext/dm68/common.c +797 -0
- data/ext/dm68/common.h +568 -0
- data/ext/dm68/dm68.c +57 -0
- data/ext/dm68/dump.c +196 -0
- data/ext/dm68/extconf.rb +16 -0
- data/ext/dm68/huff.c +542 -0
- data/ext/dm68/huff.h +34 -0
- data/ext/dm68/main.c +175 -0
- data/ext/dm68/main.h +126 -0
- data/ext/dm68/msg.c +1082 -0
- data/ext/dm68/msg.h +114 -0
- data/ext/dm68/parse.c +393 -0
- data/lib/demo-reader.rb +11 -3
- data/lib/demo_reader_defrag.rb +106 -0
- data/lib/demo_reader_warsow.rb +3 -5
- data/test/demo_reader_defrag_dm_68_cpm_test.rb +29 -0
- data/test/demo_reader_defrag_dm_68_vq3_test.rb +29 -0
- data/test/fixtures/defrag/dm_68/cpm/pornchronostar_mdf.cpm_00.49.216_tyaz.germany.dm_68 +0 -0
- data/test/fixtures/defrag/dm_68/cpm/puremotion_df.cpm_00.10.600_eS-Rody.russia.dm_68 +0 -0
- data/test/fixtures/defrag/dm_68/vq3/runkull2_df.vq3_01.05.904_XunderBIRD.Germany.dm_68 +0 -0
- data/test/fixtures/defrag/dm_68/vq3/un-dead029_df.vq3_00.16.912_uN-DeaD!WiNTeR.ru.dm_68 +0 -0
- metadata +43 -9
data/ext/dm68/msg.h
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
/*
|
2
|
+
Quake III *.dm_6? Demo Specifications
|
3
|
+
|
4
|
+
Copyright (C) 2003 Andrey '[SkulleR]' Nazarov
|
5
|
+
Based on Argus and Quake II source code
|
6
|
+
Also contains some stuff from Q3A SDK
|
7
|
+
|
8
|
+
Argus is Copyright (C) 2000 Martin Otten
|
9
|
+
Quake II and Quake III are Copyright (C) 1997-2001 ID Software, Inc
|
10
|
+
|
11
|
+
This program is free software; you can redistribute it and/or
|
12
|
+
modify it under the terms of the GNU General Public License
|
13
|
+
as published by the Free Software Foundation; either version 2
|
14
|
+
of the License, or (at your option) any later version.
|
15
|
+
|
16
|
+
This program is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
19
|
+
|
20
|
+
See the GNU General Public License for more details.
|
21
|
+
|
22
|
+
You should have received a copy of the GNU General Public License
|
23
|
+
along with this program; if not, write to the Free Software
|
24
|
+
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
25
|
+
*/
|
26
|
+
|
27
|
+
//
|
28
|
+
// msg.h - message i/o subsystem API
|
29
|
+
//
|
30
|
+
|
31
|
+
#define MAX_MSGLEN 0x4000 // max length of demo message
|
32
|
+
|
33
|
+
typedef struct sizebuf_s {
|
34
|
+
qboolean allowoverflow; // if false, do a Com_Error
|
35
|
+
qboolean overflowed; // set to true if the buffer size failed
|
36
|
+
qboolean uncompressed; // don't do Huffman encoding, write raw bytes
|
37
|
+
byte *data; // pointer to message buffer, set by MSG_Init
|
38
|
+
int maxsize; // size in bytes of message buffer, set by MSG_Init
|
39
|
+
int cursize; // number of bytes written to the buffer, set by MSG_WriteBits
|
40
|
+
int readcount; // number of bytes read from the buffer, set by MSG_ReadBits
|
41
|
+
int bit; // number of bits written to or read from the buffer
|
42
|
+
} sizebuf_t;
|
43
|
+
|
44
|
+
|
45
|
+
void MSG_Init( sizebuf_t *msg, byte *buffer, int size );
|
46
|
+
void MSG_InitRaw( sizebuf_t *msg, byte *buffer, int size );
|
47
|
+
void MSG_Clear( sizebuf_t *msg );
|
48
|
+
void MSG_SetBitstream( sizebuf_t *msg );
|
49
|
+
void MSG_WriteRawData( sizebuf_t *msg, const void *data, int length );
|
50
|
+
|
51
|
+
void MSG_BeginWriting( sizebuf_t *msg );
|
52
|
+
void MSG_WriteBits( sizebuf_t *msg, int value, int bits );
|
53
|
+
void MSG_WriteData( sizebuf_t *msg, const void *data, int length );
|
54
|
+
void MSG_WriteString( sizebuf_t *msg, const char *string );
|
55
|
+
void MSG_WriteBigString( sizebuf_t *msg, const char *string );
|
56
|
+
void MSG_WriteDeltaEntity( sizebuf_t *msg, const entityState_t *from, const entityState_t *to, qboolean force );
|
57
|
+
void MSG_WriteDeltaPlayerstate( sizebuf_t *msg, const playerState_t *from, const playerState_t *to );
|
58
|
+
|
59
|
+
void MSG_BeginReading( sizebuf_t *msg );
|
60
|
+
int MSG_ReadBits( sizebuf_t *msg, int bits );
|
61
|
+
void MSG_ReadData( sizebuf_t *msg, void *data, int len );
|
62
|
+
char * MSG_ReadString( sizebuf_t *msg );
|
63
|
+
char * MSG_ReadStringLine( sizebuf_t *msg );
|
64
|
+
char * MSG_ReadBigString( sizebuf_t *msg );
|
65
|
+
void MSG_ReadDeltaEntity( sizebuf_t *msg, const entityState_t *from, entityState_t *to, int number );
|
66
|
+
void MSG_ReadDeltaPlayerstate( sizebuf_t *msg, const playerState_t *from, playerState_t *to );
|
67
|
+
|
68
|
+
#define MSG_WriteByte(msg,c) MSG_WriteBits(msg,c,8)
|
69
|
+
#define MSG_WriteShort(msg,c) MSG_WriteBits(msg,c,16)
|
70
|
+
#define MSG_WriteSignedShort(msg,c) MSG_WriteBits(msg,c,-16)
|
71
|
+
#define MSG_WriteLong(msg,c) MSG_WriteBits(msg,c,32)
|
72
|
+
|
73
|
+
static ID_INLINE int MSG_ReadByte( sizebuf_t *msg ) {
|
74
|
+
int c = MSG_ReadBits( msg, 8 ) & 0xFF;
|
75
|
+
|
76
|
+
if( msg->readcount > msg->cursize ) {
|
77
|
+
return -1;
|
78
|
+
}
|
79
|
+
|
80
|
+
return c;
|
81
|
+
}
|
82
|
+
|
83
|
+
static ID_INLINE int MSG_ReadShort( sizebuf_t *msg ) {
|
84
|
+
int c = MSG_ReadBits( msg, 16 );
|
85
|
+
|
86
|
+
if( msg->readcount > msg->cursize ) {
|
87
|
+
return -1;
|
88
|
+
}
|
89
|
+
|
90
|
+
return c;
|
91
|
+
}
|
92
|
+
|
93
|
+
static ID_INLINE int MSG_ReadSignedShort( sizebuf_t *msg ) {
|
94
|
+
int c = MSG_ReadBits( msg, -16 );
|
95
|
+
|
96
|
+
if( msg->readcount > msg->cursize ) {
|
97
|
+
return -1;
|
98
|
+
}
|
99
|
+
|
100
|
+
return c;
|
101
|
+
}
|
102
|
+
|
103
|
+
static ID_INLINE int MSG_ReadLong( sizebuf_t *msg ) {
|
104
|
+
int c = MSG_ReadBits( msg, 32 );
|
105
|
+
|
106
|
+
if( msg->readcount > msg->cursize ) {
|
107
|
+
return -1;
|
108
|
+
}
|
109
|
+
|
110
|
+
return c;
|
111
|
+
}
|
112
|
+
|
113
|
+
|
114
|
+
|
data/ext/dm68/parse.c
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
/*
|
2
|
+
Quake III *.dm_6? Demo Specifications
|
3
|
+
|
4
|
+
Copyright (C) 2003 Andrey '[SkulleR]' Nazarov
|
5
|
+
Based on Argus and Quake II source code
|
6
|
+
Also contains some stuff from Q3A SDK
|
7
|
+
|
8
|
+
Argus is Copyright (C) 2000 Martin Otten
|
9
|
+
Quake II and Quake III are Copyright (C) 1997-2001 ID Software, Inc
|
10
|
+
|
11
|
+
This program is free software; you can redistribute it and/or
|
12
|
+
modify it under the terms of the GNU General Public License
|
13
|
+
as published by the Free Software Foundation; either version 2
|
14
|
+
of the License, or (at your option) any later version.
|
15
|
+
|
16
|
+
This program is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
19
|
+
|
20
|
+
See the GNU General Public License for more details.
|
21
|
+
|
22
|
+
You should have received a copy of the GNU General Public License
|
23
|
+
along with this program; if not, write to the Free Software
|
24
|
+
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
25
|
+
*/
|
26
|
+
|
27
|
+
//
|
28
|
+
// parse.c - illustrates how to parse the demo file
|
29
|
+
//
|
30
|
+
|
31
|
+
#include "main.h"
|
32
|
+
|
33
|
+
/*
|
34
|
+
==================
|
35
|
+
Parse_GameState
|
36
|
+
==================
|
37
|
+
*/
|
38
|
+
static void Parse_GameState( sizebuf_t *msg ) {
|
39
|
+
int c;
|
40
|
+
int index;
|
41
|
+
char *configString;
|
42
|
+
|
43
|
+
memset( &ds, 0, sizeof( ds ) );
|
44
|
+
|
45
|
+
ds.lastServerCommandNum = MSG_ReadLong( msg );
|
46
|
+
ds.currentServerCommandNum = ds.lastServerCommandNum;
|
47
|
+
|
48
|
+
while( 1 ) {
|
49
|
+
c = MSG_ReadByte( msg );
|
50
|
+
|
51
|
+
if( c == -1 ) {
|
52
|
+
Com_Error( ERR_DROP, "Parse_GameState: read past end of demo message" );
|
53
|
+
}
|
54
|
+
|
55
|
+
if( c == SVC_EOM ) {
|
56
|
+
break;
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
switch( c ) {
|
61
|
+
default:
|
62
|
+
Com_Error( ERR_DROP, "Parse_GameState: bad command byte" );
|
63
|
+
break;
|
64
|
+
|
65
|
+
case SVC_CONFIGSTRING:
|
66
|
+
index = MSG_ReadShort( msg );
|
67
|
+
if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
|
68
|
+
Com_Error( ERR_DROP, "Parse_GameState: configString index %i out of range", index );
|
69
|
+
}
|
70
|
+
configString = MSG_ReadBigString( msg );
|
71
|
+
Com_InsertIntoGameState( &ds.gameState, index, configString );
|
72
|
+
break;
|
73
|
+
|
74
|
+
case SVC_BASELINE:
|
75
|
+
index = MSG_ReadBits( msg, GENTITYNUM_BITS );
|
76
|
+
if( index < 0 || index >= MAX_GENTITIES ) {
|
77
|
+
Com_Error( ERR_DROP, "Parse_GameState: baseline index %i out of range", index );
|
78
|
+
}
|
79
|
+
MSG_ReadDeltaEntity( msg, NULL, &ds.baselines[index], index );
|
80
|
+
break;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
// FIXME: unknown stuff, not used in demos?
|
85
|
+
MSG_ReadLong( msg );
|
86
|
+
MSG_ReadLong( msg );
|
87
|
+
|
88
|
+
demo.gameStatesParsed++;
|
89
|
+
|
90
|
+
}
|
91
|
+
|
92
|
+
/*
|
93
|
+
=====================================================================
|
94
|
+
|
95
|
+
ACTION MESSAGES
|
96
|
+
|
97
|
+
=====================================================================
|
98
|
+
*/
|
99
|
+
|
100
|
+
/*
|
101
|
+
=====================
|
102
|
+
Parse_ServerCommand
|
103
|
+
=====================
|
104
|
+
*/
|
105
|
+
static void Parse_ServerCommand( sizebuf_t *msg ) {
|
106
|
+
int number;
|
107
|
+
char *string;
|
108
|
+
|
109
|
+
number = MSG_ReadLong( msg );
|
110
|
+
string = MSG_ReadString( msg );
|
111
|
+
|
112
|
+
if( number < ds.lastServerCommandNum ) {
|
113
|
+
return; // we have already received this command
|
114
|
+
}
|
115
|
+
ds.lastServerCommandNum = number;
|
116
|
+
|
117
|
+
// archive the command to be processed later
|
118
|
+
Q_strncpyz( ds.serverCommands[number & SERVERCMD_MASK], string, sizeof( ds.serverCommands[0] ) );
|
119
|
+
}
|
120
|
+
|
121
|
+
/*
|
122
|
+
==================
|
123
|
+
Parse_DeltaEntity
|
124
|
+
|
125
|
+
Parses deltas from the given base and adds the resulting entity
|
126
|
+
to the current frame
|
127
|
+
==================
|
128
|
+
*/
|
129
|
+
static void Parse_DeltaEntity( sizebuf_t *msg, snapshot_t *frame, int newnum, entityState_t *old, qboolean unchanged ) {
|
130
|
+
entityState_t *state;
|
131
|
+
|
132
|
+
state = &ds.parseEntities[ds.firstParseEntity & PARSE_ENTITIES_MASK];
|
133
|
+
|
134
|
+
if( unchanged ) {
|
135
|
+
memcpy( state, old, sizeof( *state ) ); // don't read any bits
|
136
|
+
} else {
|
137
|
+
MSG_ReadDeltaEntity( msg, old, state, newnum );
|
138
|
+
if( state->number == ENTITYNUM_NONE ) {
|
139
|
+
// the entity present in oldframe is not in the current frame
|
140
|
+
return;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
ds.firstParseEntity++;
|
145
|
+
frame->numEntities++;
|
146
|
+
}
|
147
|
+
|
148
|
+
/*
|
149
|
+
==================
|
150
|
+
Parse_PacketEntities
|
151
|
+
|
152
|
+
An svc_packetentities has just been parsed, deal with the
|
153
|
+
rest of the data stream.
|
154
|
+
==================
|
155
|
+
*/
|
156
|
+
static void Parse_PacketEntities( sizebuf_t *msg, snapshot_t *oldframe, snapshot_t *newframe ) {
|
157
|
+
int newnum;
|
158
|
+
entityState_t *oldstate;
|
159
|
+
int oldindex, oldnum;
|
160
|
+
|
161
|
+
newframe->firstEntity = ds.firstParseEntity;
|
162
|
+
newframe->numEntities = 0;
|
163
|
+
|
164
|
+
// delta from the entities present in oldframe
|
165
|
+
oldindex = 0;
|
166
|
+
if( !oldframe ) {
|
167
|
+
oldnum = 99999;
|
168
|
+
} else {
|
169
|
+
if( oldindex >= oldframe->numEntities ) {
|
170
|
+
oldnum = 99999;
|
171
|
+
} else {
|
172
|
+
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
|
173
|
+
oldnum = oldstate->number;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
while( 1 ) {
|
178
|
+
newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
|
179
|
+
if( newnum < 0 || newnum >= MAX_GENTITIES ) {
|
180
|
+
Com_Error( ERR_DROP, "Parse_PacketEntities: bad number %i", newnum );
|
181
|
+
}
|
182
|
+
|
183
|
+
if( msg->readcount > msg->cursize ) {
|
184
|
+
Com_Error( ERR_DROP, "Parse_PacketEntities: end of message" );
|
185
|
+
}
|
186
|
+
|
187
|
+
if( newnum == ENTITYNUM_NONE ) {
|
188
|
+
break; // end of packetentities
|
189
|
+
}
|
190
|
+
|
191
|
+
while( oldnum < newnum ) {
|
192
|
+
// one or more entities from the old packet are unchanged
|
193
|
+
Parse_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
|
194
|
+
|
195
|
+
oldindex++;
|
196
|
+
|
197
|
+
if( oldindex >= oldframe->numEntities ) {
|
198
|
+
oldnum = 99999;
|
199
|
+
} else {
|
200
|
+
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
|
201
|
+
oldnum = oldstate->number;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
if( oldnum == newnum ) {
|
206
|
+
// delta from previous state
|
207
|
+
Parse_DeltaEntity( msg, newframe, newnum, oldstate, qfalse );
|
208
|
+
|
209
|
+
oldindex++;
|
210
|
+
|
211
|
+
if( oldindex >= oldframe->numEntities ) {
|
212
|
+
oldnum = 99999;
|
213
|
+
} else {
|
214
|
+
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
|
215
|
+
oldnum = oldstate->number;
|
216
|
+
}
|
217
|
+
continue;
|
218
|
+
}
|
219
|
+
|
220
|
+
if( oldnum > newnum ) {
|
221
|
+
// delta from baseline
|
222
|
+
Parse_DeltaEntity( msg, newframe, newnum, &ds.baselines[newnum], qfalse );
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
// any remaining entities in the old frame are copied over
|
227
|
+
while( oldnum != 99999 ) {
|
228
|
+
// one or more entities from the old packet are unchanged
|
229
|
+
Parse_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
|
230
|
+
|
231
|
+
oldindex++;
|
232
|
+
|
233
|
+
if( oldindex >= oldframe->numEntities ) {
|
234
|
+
oldnum = 99999;
|
235
|
+
} else {
|
236
|
+
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
|
237
|
+
oldnum = oldstate->number;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
/*
|
243
|
+
=====================
|
244
|
+
ParseSnapshot
|
245
|
+
=====================
|
246
|
+
*/
|
247
|
+
static void Parse_Snapshot( sizebuf_t *msg ) {
|
248
|
+
snapshot_t *oldsnap;
|
249
|
+
int delta;
|
250
|
+
int len;
|
251
|
+
|
252
|
+
// save the frame off in the backup array for later delta comparisons
|
253
|
+
ds.snap = &ds.snapshots[demo.demoMessageSequence & SNAPSHOT_MASK];
|
254
|
+
memset( ds.snap, 0, sizeof( *ds.snap ) );
|
255
|
+
|
256
|
+
ds.snap->seq = demo.demoMessageSequence;
|
257
|
+
ds.snap->serverTime = MSG_ReadLong( msg );
|
258
|
+
|
259
|
+
// If the frame is delta compressed from data that we
|
260
|
+
// no longer have available, we must suck up the rest of
|
261
|
+
// the frame, but not use it, then ask for a non-compressed
|
262
|
+
// message
|
263
|
+
delta = MSG_ReadByte( msg );
|
264
|
+
if( delta ) {
|
265
|
+
ds.snap->deltaSeq = demo.demoMessageSequence - delta;
|
266
|
+
oldsnap = &ds.snapshots[ds.snap->deltaSeq & SNAPSHOT_MASK];
|
267
|
+
|
268
|
+
if( !oldsnap->valid ) {
|
269
|
+
// should never happen
|
270
|
+
Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" );
|
271
|
+
} else if( oldsnap->seq != ds.snap->deltaSeq ) {
|
272
|
+
// The frame that the server did the delta from
|
273
|
+
// is too old, so we can't reconstruct it properly.
|
274
|
+
Com_Printf( "Delta frame too old.\n" );
|
275
|
+
} else if( ds.firstParseEntity - oldsnap->firstEntity >
|
276
|
+
MAX_PARSE_ENTITIES - MAX_ENTITIES_IN_SNAPSHOT )
|
277
|
+
{
|
278
|
+
Com_Printf( "Delta parse_entities too old.\n" );
|
279
|
+
} else {
|
280
|
+
ds.snap->valid = qtrue; // valid delta parse
|
281
|
+
}
|
282
|
+
} else {
|
283
|
+
oldsnap = NULL;
|
284
|
+
ds.snap->deltaSeq = -1;
|
285
|
+
ds.snap->valid = qtrue; // uncompressed frame
|
286
|
+
}
|
287
|
+
|
288
|
+
// read snapFlags
|
289
|
+
ds.snap->snapFlags = MSG_ReadByte( msg );
|
290
|
+
|
291
|
+
// read areabits
|
292
|
+
len = MSG_ReadByte( msg );
|
293
|
+
MSG_ReadData( msg, ds.snap->areamask, len );
|
294
|
+
|
295
|
+
// read playerinfo
|
296
|
+
MSG_ReadDeltaPlayerstate( msg, oldsnap ? &oldsnap->ps : NULL, &ds.snap->ps );
|
297
|
+
|
298
|
+
// read packet entities
|
299
|
+
Parse_PacketEntities( msg, oldsnap, ds.snap );
|
300
|
+
}
|
301
|
+
|
302
|
+
/*
|
303
|
+
=====================
|
304
|
+
Parse_DemoMessage
|
305
|
+
=====================
|
306
|
+
*/
|
307
|
+
static void Parse_DemoMessage( sizebuf_t *msg ) {
|
308
|
+
int cmd;
|
309
|
+
|
310
|
+
// remaining data is Huffman compressed
|
311
|
+
MSG_SetBitstream( msg );
|
312
|
+
|
313
|
+
MSG_ReadLong( msg ); // FIXME: not used in demos
|
314
|
+
|
315
|
+
|
316
|
+
//
|
317
|
+
// parse the message
|
318
|
+
//
|
319
|
+
while( 1 ) {
|
320
|
+
if( msg->readcount > msg->cursize ) {
|
321
|
+
Com_Error( ERR_DROP, "Parse_DemoMessage: read past end of demo message" );
|
322
|
+
break;
|
323
|
+
}
|
324
|
+
|
325
|
+
cmd = MSG_ReadByte( msg );
|
326
|
+
|
327
|
+
if( cmd == SVC_EOM ) {
|
328
|
+
break;
|
329
|
+
}
|
330
|
+
|
331
|
+
// other commands
|
332
|
+
switch( cmd ) {
|
333
|
+
default:
|
334
|
+
case SVC_BASELINE:
|
335
|
+
case SVC_CONFIGSTRING:
|
336
|
+
case SVC_DOWNLOAD:
|
337
|
+
Com_Error( ERR_DROP, "Parse_DemoMessage: illegible demo message" );
|
338
|
+
break;
|
339
|
+
case SVC_NOP: /* do nothing */ break;
|
340
|
+
case SVC_GAMESTATE: Parse_GameState( msg ); break;
|
341
|
+
case SVC_SERVERCOMMAND: Parse_ServerCommand( msg ); break;
|
342
|
+
case SVC_SNAPSHOT: Parse_Snapshot( msg ); break;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
}
|
347
|
+
|
348
|
+
/*
|
349
|
+
=====================
|
350
|
+
Parse_NextDemoMessage
|
351
|
+
|
352
|
+
Read next message from demo file and parse it
|
353
|
+
Return qfalse if demo EOF reached or error occured
|
354
|
+
=====================
|
355
|
+
*/
|
356
|
+
qboolean Parse_NextDemoMessage( void ) {
|
357
|
+
sizebuf_t msg;
|
358
|
+
byte buffer[MAX_MSGLEN];
|
359
|
+
int len;
|
360
|
+
int seq;
|
361
|
+
|
362
|
+
if( fread( &seq, 1, 4, demo.demofile ) != 4 ) {
|
363
|
+
Com_Printf( "Demo file was truncated\n" );
|
364
|
+
return qfalse;
|
365
|
+
}
|
366
|
+
|
367
|
+
if( fread( &len, 1, 4, demo.demofile ) != 4 ) {
|
368
|
+
Com_Printf( "Demo file was truncated\n" );
|
369
|
+
return qfalse;
|
370
|
+
}
|
371
|
+
|
372
|
+
if( seq == -1 || len == -1 ) {
|
373
|
+
return qfalse; // demo EOF reached
|
374
|
+
}
|
375
|
+
|
376
|
+
MSG_Init( &msg, buffer, sizeof( buffer ) );
|
377
|
+
|
378
|
+
demo.demoMessageSequence = LittleLong( seq );
|
379
|
+
msg.cursize = LittleLong( len );
|
380
|
+
|
381
|
+
if( msg.cursize <= 0 || msg.cursize >= msg.maxsize ) {
|
382
|
+
Com_Error( ERR_DROP, "Illegal demo message length" );
|
383
|
+
}
|
384
|
+
|
385
|
+
if( fread( msg.data, 1, msg.cursize, demo.demofile ) != msg.cursize ) {
|
386
|
+
Com_Printf( "Demo file was truncated\n" );
|
387
|
+
return qfalse;
|
388
|
+
}
|
389
|
+
|
390
|
+
Parse_DemoMessage( &msg ); // parse the message
|
391
|
+
|
392
|
+
return qtrue;
|
393
|
+
}
|