]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/main.c
compiles again on win32, trashed mhash
[xonotic/netradiant.git] / tools / quake3 / q3map2 / main.c
1 /* -------------------------------------------------------------------------------
2
3 Copyright (C) 1999-2007 id Software, Inc. and contributors.
4 For a list of contributors, see the accompanying CONTRIBUTORS file.
5
6 This file is part of GtkRadiant.
7
8 GtkRadiant is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 GtkRadiant is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with GtkRadiant; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21
22 -------------------------------------------------------------------------------
23
24 This code has been altered significantly from its original form, to support
25 several games based on the Quake III Arena engine, in the form of "Q3Map2."
26
27 ------------------------------------------------------------------------------- */
28
29
30
31 /* marker */
32 #define MAIN_C
33
34
35
36 /* dependencies */
37 #include "q3map2.h"
38
39 /*
40 Random()
41 returns a pseudorandom number between 0 and 1
42 */
43
44 vec_t Random( void )
45 {
46         return (vec_t) rand() / RAND_MAX;
47 }
48
49
50
51 /*
52 ExitQ3Map()
53 cleanup routine
54 */
55
56 static void ExitQ3Map( void )
57 {
58         BSPFilesCleanup();
59         if( mapDrawSurfs != NULL )
60                 free( mapDrawSurfs );
61 }
62
63 static int MD4BlockChecksum( void * buffer, int length ) {
64         unsigned char digest[16];
65         int checksum;
66
67         md4_get_digest( buffer, length, digest );
68         /* I suppose it has to be done that way for legacy reasons? */
69         checksum = digest[0] & ( digest[1] << 8 ) & ( digest[2] << 16 ) & ( digest[3] << 24 );
70         checksum ^= digest[4] & ( digest[5] << 8 ) & ( digest[6] << 16 ) & ( digest[7] << 24 );
71         checksum ^= digest[8] & ( digest[9] << 8 ) & ( digest[10] << 16 ) & ( digest[11] << 24 );
72         checksum ^= digest[12] & ( digest[13] << 8 ) & ( digest[14] << 16 ) & ( digest[15] << 24 );
73         return checksum;
74 }
75
76 /*
77 FixAAS()
78 resets an aas checksum to match the given BSP
79 */
80
81 int FixAAS( int argc, char **argv )
82 {
83         int                     length, checksum;
84         void            *buffer;
85         FILE            *file;
86         char            aas[ 1024 ], **ext;
87         char            *exts[] =
88                                 {
89                                         ".aas",
90                                         "_b0.aas",
91                                         "_b1.aas",
92                                         NULL
93                                 };
94         
95         
96         /* arg checking */
97         if( argc < 2 )
98         {
99                 Sys_Printf( "Usage: q3map -fixaas [-v] <mapname>\n" );
100                 return 0;
101         }
102         
103         /* do some path mangling */
104         strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
105         StripExtension( source );
106         DefaultExtension( source, ".bsp" );
107         
108         /* note it */
109         Sys_Printf( "--- FixAAS ---\n" );
110         
111         /* load the bsp */
112         Sys_Printf( "Loading %s\n", source );
113         length = LoadFile( source, &buffer );
114         
115         /* create bsp checksum */
116         Sys_Printf( "Creating checksum...\n" );
117         checksum = LittleLong( MD4BlockChecksum( buffer, length ) );
118         
119         /* write checksum to aas */
120         ext = exts;
121         while( *ext )
122         {
123                 /* mangle name */
124                 strcpy( aas, source );
125                 StripExtension( aas );
126                 strcat( aas, *ext );
127                 Sys_Printf( "Trying %s\n", aas );
128                 ext++;
129                 
130                 /* fix it */
131                 file = fopen( aas, "r+b" );
132                 if( !file )
133                         continue;
134                 if( fwrite( &checksum, 4, 1, file ) != 1 )
135                         Error( "Error writing checksum to %s", aas );
136                 fclose( file );
137         }
138         
139         /* return to sender */
140         return 0;
141 }
142
143
144
145 /*
146 AnalyzeBSP() - ydnar
147 analyzes a Quake engine BSP file
148 */
149
150 typedef struct abspHeader_s
151 {
152         char                    ident[ 4 ];
153         int                             version;
154         
155         bspLump_t               lumps[ 1 ];     /* unknown size */
156 }
157 abspHeader_t;
158
159 typedef struct abspLumpTest_s
160 {
161         int                             radix, minCount;
162         char                    *name;
163 }
164 abspLumpTest_t;
165
166 int AnalyzeBSP( int argc, char **argv )
167 {
168         abspHeader_t                    *header;
169         int                                             size, i, version, offset, length, lumpInt, count;
170         char                                    ident[ 5 ];
171         void                                    *lump;
172         float                                   lumpFloat;
173         char                                    lumpString[ 1024 ], source[ 1024 ];
174         qboolean                                lumpSwap = qfalse;
175         abspLumpTest_t                  *lumpTest;
176         static abspLumpTest_t   lumpTests[] =
177                                                         {
178                                                                 { sizeof( bspPlane_t ),                 6,              "IBSP LUMP_PLANES" },
179                                                                 { sizeof( bspBrush_t ),                 1,              "IBSP LUMP_BRUSHES" },
180                                                                 { 8,                                                    6,              "IBSP LUMP_BRUSHSIDES" },
181                                                                 { sizeof( bspBrushSide_t ),             6,              "RBSP LUMP_BRUSHSIDES" },
182                                                                 { sizeof( bspModel_t ),                 1,              "IBSP LUMP_MODELS" },
183                                                                 { sizeof( bspNode_t ),                  2,              "IBSP LUMP_NODES" },
184                                                                 { sizeof( bspLeaf_t ),                  1,              "IBSP LUMP_LEAFS" },
185                                                                 { 104,                                                  3,              "IBSP LUMP_DRAWSURFS" },
186                                                                 { 44,                                                   3,              "IBSP LUMP_DRAWVERTS" },
187                                                                 { 4,                                                    6,              "IBSP LUMP_DRAWINDEXES" },
188                                                                 { 128 * 128 * 3,                                1,              "IBSP LUMP_LIGHTMAPS" },
189                                                                 { 256 * 256 * 3,                                1,              "IBSP LUMP_LIGHTMAPS (256 x 256)" },
190                                                                 { 512 * 512 * 3,                                1,              "IBSP LUMP_LIGHTMAPS (512 x 512)" },
191                                                                 { 0, 0, NULL }
192                                                         };
193         
194         
195         /* arg checking */
196         if( argc < 1 )
197         {
198                 Sys_Printf( "Usage: q3map -analyze [-lumpswap] [-v] <mapname>\n" );
199                 return 0;
200         }
201         
202         /* process arguments */
203         for( i = 1; i < (argc - 1); i++ )
204         {
205                 /* -format map|ase|... */
206                 if( !strcmp( argv[ i ],  "-lumpswap" ) )
207                 {
208                         Sys_Printf( "Swapped lump structs enabled\n" );
209                         lumpSwap = qtrue;
210                 }
211         }
212         
213         /* clean up map name */
214         strcpy( source, ExpandArg( argv[ i ] ) );
215         Sys_Printf( "Loading %s\n", source );
216         
217         /* load the file */
218         size = LoadFile( source, (void**) &header );
219         if( size == 0 || header == NULL )
220         {
221                 Sys_Printf( "Unable to load %s.\n", source );
222                 return -1;
223         }
224         
225         /* analyze ident/version */
226         memcpy( ident, header->ident, 4 );
227         ident[ 4 ] = '\0';
228         version = LittleLong( header->version );
229         
230         Sys_Printf( "Identity:      %s\n", ident );
231         Sys_Printf( "Version:       %d\n", version );
232         Sys_Printf( "---------------------------------------\n" );
233         
234         /* analyze each lump */
235         for( i = 0; i < 100; i++ )
236         {
237                 /* call of duty swapped lump pairs */
238                 if( lumpSwap )
239                 {
240                         offset = LittleLong( header->lumps[ i ].length );
241                         length = LittleLong( header->lumps[ i ].offset );
242                 }
243                 
244                 /* standard lump pairs */
245                 else
246                 {
247                         offset = LittleLong( header->lumps[ i ].offset );
248                         length = LittleLong( header->lumps[ i ].length );
249                 }
250                 
251                 /* extract data */
252                 lump = (byte*) header + offset;
253                 lumpInt = LittleLong( (int) *((int*) lump) );
254                 lumpFloat = LittleFloat( (float) *((float*) lump) );
255                 memcpy( lumpString, (char*) lump, (length < 1024 ? length : 1024) );
256                 lumpString[ 1024 ] = '\0';
257                 
258                 /* print basic lump info */
259                 Sys_Printf( "Lump:          %d\n", i );
260                 Sys_Printf( "Offset:        %d bytes\n", offset );
261                 Sys_Printf( "Length:        %d bytes\n", length );
262                 
263                 /* only operate on valid lumps */
264                 if( length > 0 )
265                 {
266                         /* print data in 4 formats */
267                         Sys_Printf( "As hex:        %08X\n", lumpInt );
268                         Sys_Printf( "As int:        %d\n", lumpInt );
269                         Sys_Printf( "As float:      %f\n", lumpFloat );
270                         Sys_Printf( "As string:     %s\n", lumpString );
271                         
272                         /* guess lump type */
273                         if( lumpString[ 0 ] == '{' && lumpString[ 2 ] == '"' )
274                                 Sys_Printf( "Type guess:    IBSP LUMP_ENTITIES\n" );
275                         else if( strstr( lumpString, "textures/" ) )
276                                 Sys_Printf( "Type guess:    IBSP LUMP_SHADERS\n" );
277                         else
278                         {
279                                 /* guess based on size/count */
280                                 for( lumpTest = lumpTests; lumpTest->radix > 0; lumpTest++ )
281                                 {
282                                         if( (length % lumpTest->radix) != 0 )
283                                                 continue;
284                                         count = length / lumpTest->radix;
285                                         if( count < lumpTest->minCount )
286                                                 continue;
287                                         Sys_Printf( "Type guess:    %s (%d x %d)\n", lumpTest->name, count, lumpTest->radix );
288                                 }
289                         }
290                 }
291                 
292                 Sys_Printf( "---------------------------------------\n" );
293                 
294                 /* end of file */
295                 if( offset + length >= size )
296                         break;
297         }
298         
299         /* last stats */
300         Sys_Printf( "Lump count:    %d\n", i + 1 );
301         Sys_Printf( "File size:     %d bytes\n", size );
302         
303         /* return to caller */
304         return 0;
305 }
306
307
308
309 /*
310 BSPInfo()
311 emits statistics about the bsp file
312 */
313
314 int BSPInfo( int count, char **fileNames )
315 {
316         int                     i;
317         char            source[ 1024 ], ext[ 64 ];
318         int                     size;
319         FILE            *f;
320         
321         
322         /* dummy check */
323         if( count < 1 )
324         {
325                 Sys_Printf( "No files to dump info for.\n");
326                 return -1;
327         }
328         
329         /* enable info mode */
330         infoMode = qtrue;
331         
332         /* walk file list */
333         for( i = 0; i < count; i++ )
334         {
335                 Sys_Printf( "---------------------------------\n" );
336                 
337                 /* mangle filename and get size */
338                 strcpy( source, fileNames[ i ] );
339                 ExtractFileExtension( source, ext );
340                 if( !Q_stricmp( ext, "map" ) )
341                         StripExtension( source );
342                 DefaultExtension( source, ".bsp" );
343                 f = fopen( source, "rb" );
344                 if( f )
345                 {
346                         size = Q_filelength (f);
347                         fclose( f );
348                 }
349                 else
350                         size = 0;
351                 
352                 /* load the bsp file and print lump sizes */
353                 Sys_Printf( "%s\n", source );
354                 LoadBSPFile( source );          
355                 PrintBSPFileSizes();
356                 
357                 /* print sizes */
358                 Sys_Printf( "\n" );
359                 Sys_Printf( "          total         %9d\n", size );
360                 Sys_Printf( "                        %9d KB\n", size / 1024 );
361                 Sys_Printf( "                        %9d MB\n", size / (1024 * 1024) );
362                 
363                 Sys_Printf( "---------------------------------\n" );
364         }
365         
366         /* return count */
367         return i;
368 }
369
370
371
372 /*
373 ScaleBSPMain()
374 amaze and confuse your enemies with wierd scaled maps!
375 */
376
377 int ScaleBSPMain( int argc, char **argv )
378 {
379         int                     i;
380         float           f, scale;
381         vec3_t          vec;
382         char            str[ 1024 ];
383         
384         
385         /* arg checking */
386         if( argc < 2 )
387         {
388                 Sys_Printf( "Usage: q3map -scale <value> [-v] <mapname>\n" );
389                 return 0;
390         }
391         
392         /* get scale */
393         scale = atof( argv[ argc - 2 ] );
394         if( scale == 0.0f )
395         {
396                 Sys_Printf( "Usage: q3map -scale <value> [-v] <mapname>\n" );
397                 Sys_Printf( "Non-zero scale value required.\n" );
398                 return 0;
399         }
400         
401         /* do some path mangling */
402         strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
403         StripExtension( source );
404         DefaultExtension( source, ".bsp" );
405         
406         /* load the bsp */
407         Sys_Printf( "Loading %s\n", source );
408         LoadBSPFile( source );
409         ParseEntities();
410         
411         /* note it */
412         Sys_Printf( "--- ScaleBSP ---\n" );
413         Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities );
414         
415         /* scale entity keys */
416         for( i = 0; i < numBSPEntities && i < numEntities; i++ )
417         {
418                 /* scale origin */
419                 GetVectorForKey( &entities[ i ], "origin", vec );
420                 if( (vec[ 0 ] + vec[ 1 ] + vec[ 2 ]) )
421                 {
422                         VectorScale( vec, scale, vec );
423                         sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] );
424                         SetKeyValue( &entities[ i ], "origin", str );
425                 }
426                 
427                 /* scale door lip */
428                 f = FloatForKey( &entities[ i ], "lip" );
429                 if( f )
430                 {
431                         f *= scale;
432                         sprintf( str, "%f", f );
433                         SetKeyValue( &entities[ i ], "lip", str );
434                 }
435         }
436         
437         /* scale models */
438         for( i = 0; i < numBSPModels; i++ )
439         {
440                 VectorScale( bspModels[ i ].mins, scale, bspModels[ i ].mins );
441                 VectorScale( bspModels[ i ].maxs, scale, bspModels[ i ].maxs );
442         }
443         
444         /* scale nodes */
445         for( i = 0; i < numBSPNodes; i++ )
446         {
447                 VectorScale( bspNodes[ i ].mins, scale, bspNodes[ i ].mins );
448                 VectorScale( bspNodes[ i ].maxs, scale, bspNodes[ i ].maxs );
449         }
450         
451         /* scale leafs */
452         for( i = 0; i < numBSPLeafs; i++ )
453         {
454                 VectorScale( bspLeafs[ i ].mins, scale, bspLeafs[ i ].mins );
455                 VectorScale( bspLeafs[ i ].maxs, scale, bspLeafs[ i ].maxs );
456         }
457         
458         /* scale drawverts */
459         for( i = 0; i < numBSPDrawVerts; i++ )
460                 VectorScale( bspDrawVerts[ i ].xyz, scale, bspDrawVerts[ i ].xyz );
461         
462         /* scale planes */
463         for( i = 0; i < numBSPPlanes; i++ )
464                 bspPlanes[ i ].dist *= scale;
465         
466         /* scale gridsize */
467         GetVectorForKey( &entities[ 0 ], "gridsize", vec );
468         if( (vec[ 0 ] + vec[ 1 ] + vec[ 2 ]) == 0.0f )
469                 VectorCopy( gridSize, vec );
470         VectorScale( vec, scale, vec );
471         sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] );
472         SetKeyValue( &entities[ 0 ], "gridsize", str );
473         
474         /* write the bsp */
475         UnparseEntities();
476         StripExtension( source );
477         DefaultExtension( source, "_s.bsp" );
478         Sys_Printf( "Writing %s\n", source );
479         WriteBSPFile( source );
480         
481         /* return to sender */
482         return 0;
483 }
484
485
486
487 /*
488 ConvertBSPMain()
489 main argument processing function for bsp conversion
490 */
491
492 int ConvertBSPMain( int argc, char **argv )
493 {
494         int             i;
495         int             (*convertFunc)( char * );
496         game_t  *convertGame;
497         
498         
499         /* set default */
500         convertFunc = ConvertBSPToASE;
501         convertGame = NULL;
502         
503         /* arg checking */
504         if( argc < 1 )
505         {
506                 Sys_Printf( "Usage: q3map -scale <value> [-v] <mapname>\n" );
507                 return 0;
508         }
509         
510         /* process arguments */
511         for( i = 1; i < (argc - 1); i++ )
512         {
513                 /* -format map|ase|... */
514                 if( !strcmp( argv[ i ],  "-format" ) )
515                 {
516                         i++;
517                         if( !Q_stricmp( argv[ i ], "ase" ) )
518                                 convertFunc = ConvertBSPToASE;
519                         else if( !Q_stricmp( argv[ i ], "map" ) )
520                                 convertFunc = ConvertBSPToMap;
521                         else
522                         {
523                                 convertGame = GetGame( argv[ i ] );
524                                 if( convertGame == NULL )
525                                         Sys_Printf( "Unknown conversion format \"%s\". Defaulting to ASE.\n", argv[ i ] );
526                         }
527                 }
528         }
529         
530         /* clean up map name */
531         strcpy( source, ExpandArg( argv[ i ] ) );
532         StripExtension( source );
533         DefaultExtension( source, ".bsp" );
534         
535         LoadShaderInfo();
536         
537         Sys_Printf( "Loading %s\n", source );
538         
539         /* ydnar: load surface file */
540         //%     LoadSurfaceExtraFile( source );
541         
542         LoadBSPFile( source );
543         
544         /* parse bsp entities */
545         ParseEntities();
546         
547         /* bsp format convert? */
548         if( convertGame != NULL )
549         {
550                 /* set global game */
551                 game = convertGame;
552                 
553                 /* write bsp */
554                 StripExtension( source );
555                 DefaultExtension( source, "_c.bsp" );
556                 Sys_Printf( "Writing %s\n", source );
557                 WriteBSPFile( source );
558                 
559                 /* return to sender */
560                 return 0;
561         }
562         
563         /* normal convert */
564         return convertFunc( source );
565 }
566
567
568
569 /*
570 main()
571 q3map mojo...
572 */
573
574 int main( int argc, char **argv )
575 {
576         int             i, r;
577         double  start, end;
578         
579         
580         /* we want consistent 'randomness' */
581         srand( 0 );
582         
583         /* start timer */
584         start = I_FloatTime();
585
586         /* this was changed to emit version number over the network */
587         printf( Q3MAP_VERSION "\n" );
588         
589         /* set exit call */
590         atexit( ExitQ3Map );
591         
592         /* read general options first */
593         for( i = 1; i < argc; i++ )
594         {
595                 /* -connect */
596                 if( !strcmp( argv[ i ], "-connect" ) )
597                 {
598                         argv[ i ] = NULL;
599                         i++;
600                         Broadcast_Setup( argv[ i ] );
601                         argv[ i ] = NULL;
602                 }
603                 
604                 /* verbose */
605                 else if( !strcmp( argv[ i ], "-v" ) )
606                 {
607                         verbose = qtrue;
608                         argv[ i ] = NULL;
609                 }
610                 
611                 /* force */
612                 else if( !strcmp( argv[ i ], "-force" ) )
613                 {
614                         force = qtrue;
615                         argv[ i ] = NULL;
616                 }
617                 
618                 /* patch subdivisions */
619                 else if( !strcmp( argv[ i ], "-subdivisions" ) )
620                 {
621                         argv[ i ] = NULL;
622                         i++;
623                         patchSubdivisions = atoi( argv[ i ] );
624                         argv[ i ] = NULL;
625                         if( patchSubdivisions <= 0 )
626                                 patchSubdivisions = 1;
627                 }
628                 
629                 /* threads */
630                 else if( !strcmp( argv[ i ], "-threads" ) )
631                 {
632                         argv[ i ] = NULL;
633                         i++;
634                         numthreads = atoi( argv[ i ] );
635                         argv[ i ] = NULL;
636                 }
637         }
638         
639         /* init model library */
640         PicoInit();
641         PicoSetMallocFunc( safe_malloc );
642         PicoSetFreeFunc( free );
643         PicoSetPrintFunc( PicoPrintFunc );
644         PicoSetLoadFileFunc( PicoLoadFileFunc );
645         PicoSetFreeFileFunc( free );
646         
647         /* set number of threads */
648         ThreadSetDefault();
649         
650         /* generate sinusoid jitter table */
651         for( i = 0; i < MAX_JITTERS; i++ )
652         {
653                 jitters[ i ] = sin( i * 139.54152147 );
654                 //%     Sys_Printf( "Jitter %4d: %f\n", i, jitters[ i ] );
655         }
656         
657         /* we print out two versions, q3map's main version (since it evolves a bit out of GtkRadiant)
658            and we put the GtkRadiant version to make it easy to track with what version of Radiant it was built with */
659         
660         Sys_Printf( "Q3Map         - v1.0r (c) 1999 Id Software Inc.\n" );
661         Sys_Printf( "Q3Map (ydnar) - v" Q3MAP_VERSION "\n" );
662         Sys_Printf( "GtkRadiant    - v" RADIANT_VERSION " " __DATE__ " " __TIME__ "\n" );
663         Sys_Printf( "%s\n", Q3MAP_MOTD );
664         
665         /* ydnar: new path initialization */
666         InitPaths( &argc, argv );
667         
668         /* check if we have enough options left to attempt something */
669         if( argc < 2 )
670                 Error( "Usage: %s [general options] [options] mapfile", argv[ 0 ] );
671         
672         /* fixaas */
673         if( !strcmp( argv[ 1 ], "-fixaas" ) )
674                 r = FixAAS( argc - 1, argv + 1 );
675         
676         /* analyze */
677         else if( !strcmp( argv[ 1 ], "-analyze" ) )
678                 r = AnalyzeBSP( argc - 1, argv + 1 );
679         
680         /* info */
681         else if( !strcmp( argv[ 1 ], "-info" ) )
682                 r = BSPInfo( argc - 2, argv + 2 );
683         
684         /* vis */
685         else if( !strcmp( argv[ 1 ], "-vis" ) )
686                 r = VisMain( argc - 1, argv + 1 );
687         
688         /* light */
689         else if( !strcmp( argv[ 1 ], "-light" ) )
690                 r = LightMain( argc - 1, argv + 1 );
691         
692         /* vlight */
693         else if( !strcmp( argv[ 1 ], "-vlight" ) )
694         {
695                 Sys_Printf( "WARNING: VLight is no longer supported, defaulting to -light -fast instead\n\n" );
696                 argv[ 1 ] = "-fast";    /* eek a hack */
697                 r = LightMain( argc, argv );
698         }
699         
700         /* ydnar: lightmap export */
701         else if( !strcmp( argv[ 1 ], "-export" ) )
702                 r = ExportLightmapsMain( argc - 1, argv + 1 );
703         
704         /* ydnar: lightmap import */
705         else if( !strcmp( argv[ 1 ], "-import" ) )
706                 r = ImportLightmapsMain( argc - 1, argv + 1 );
707         
708         /* ydnar: bsp scaling */
709         else if( !strcmp( argv[ 1 ], "-scale" ) )
710                 r = ScaleBSPMain( argc - 1, argv + 1 );
711         
712         /* ydnar: bsp conversion */
713         else if( !strcmp( argv[ 1 ], "-convert" ) )
714                 r = ConvertBSPMain( argc - 1, argv + 1 );
715         
716         /* ydnar: otherwise create a bsp */
717         else
718                 r = BSPMain( argc, argv );
719         
720         /* emit time */
721         end = I_FloatTime();
722         Sys_Printf( "%9.0f seconds elapsed\n", end - start );
723         
724         /* shut down connection */
725         Broadcast_Shutdown();
726         
727         /* return any error code */
728         return r;
729 }