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