]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - common.c
got rid of host_parms.argc and argv, now uses com_argc and com_argv directly
[xonotic/darkplaces.git] / common.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // common.c -- misc functions used in client and server
21
22 #include <stdlib.h>
23 #include <fcntl.h>
24 #ifdef WIN32
25 #include <io.h>
26 #else
27 #include <unistd.h>
28 #endif
29
30 #include "quakedef.h"
31
32 cvar_t registered = {0, "registered","0"};
33 cvar_t cmdline = {0, "cmdline","0"};
34
35 mempool_t *pak_mempool;
36
37 qboolean com_modified;   // set true if using non-id files
38
39 qboolean msg_suppress_1 = 0;
40
41 void COM_InitFilesystem (void);
42
43 char com_token[1024];
44 int com_argc;
45 char **com_argv;
46
47 // LordHavoc: made commandline 1024 characters instead of 256
48 #define CMDLINE_LENGTH  1024
49 char com_cmdline[CMDLINE_LENGTH];
50
51 int gamemode;
52 char *gamename;
53
54 /*
55
56
57 All of Quake's data access is through a hierchal file system, but the contents of the file system can be transparently merged from several sources.
58
59 The "base directory" is the path to the directory holding the quake.exe and all game directories.  The sys_* files pass this to host_init in quakeparms_t->basedir.  This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory.  The base directory is
60 only used during filesystem initialization.
61
62 The "game directory" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to.  This can be overridden with the "-game" command line parameter.  The game directory can never be changed while quake is executing.  This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't.
63
64 The "cache directory" is only used during development to save network bandwidth, especially over ISDN / T1 lines.  If there is a cache directory
65 specified, when a file is found by the normal search path, it will be mirrored
66 into the cache directory, then opened there.
67
68
69
70 FIXME:
71 The file "parms.txt" will be read out of the game directory and appended to the current command line arguments to allow different games to initialize startup parms differently.  This could be used to add a "-sspeed 22050" for the high quality sound edition.  Because they are added at the end, they will not override an explicit setting on the original command line.
72
73 */
74
75 //============================================================================
76
77
78 /*
79 ============================================================================
80
81                                         LIBRARY REPLACEMENT FUNCTIONS
82
83 ============================================================================
84 */
85
86 int Q_strncasecmp (char *s1, char *s2, int n)
87 {
88         int             c1, c2;
89
90         while (1)
91         {
92                 c1 = *s1++;
93                 c2 = *s2++;
94
95                 if (!n--)
96                         return 0;               // strings are equal until end point
97
98                 if (c1 != c2)
99                 {
100                         if (c1 >= 'a' && c1 <= 'z')
101                                 c1 -= ('a' - 'A');
102                         if (c2 >= 'a' && c2 <= 'z')
103                                 c2 -= ('a' - 'A');
104                         if (c1 != c2)
105                                 return -1;              // strings not equal
106                 }
107                 if (!c1)
108                         return 0;               // strings are equal
109         }
110
111         return -1;
112 }
113
114 int Q_strcasecmp (char *s1, char *s2)
115 {
116         return Q_strncasecmp (s1, s2, 99999);
117 }
118
119 /*
120 ============================================================================
121
122                                         BYTE ORDER FUNCTIONS
123
124 ============================================================================
125 */
126
127 #if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
128 short   (*BigShort) (short l);
129 short   (*LittleShort) (short l);
130 int     (*BigLong) (int l);
131 int     (*LittleLong) (int l);
132 float   (*BigFloat) (float l);
133 float   (*LittleFloat) (float l);
134 #endif
135
136 short   ShortSwap (short l)
137 {
138         qbyte    b1,b2;
139
140         b1 = l&255;
141         b2 = (l>>8)&255;
142
143         return (b1<<8) + b2;
144 }
145
146 #if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
147 short   ShortNoSwap (short l)
148 {
149         return l;
150 }
151 #endif
152
153 int    LongSwap (int l)
154 {
155         qbyte    b1,b2,b3,b4;
156
157         b1 = l&255;
158         b2 = (l>>8)&255;
159         b3 = (l>>16)&255;
160         b4 = (l>>24)&255;
161
162         return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
163 }
164
165 #if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
166 int     LongNoSwap (int l)
167 {
168         return l;
169 }
170 #endif
171
172 float FloatSwap (float f)
173 {
174         union
175         {
176                 float   f;
177                 qbyte    b[4];
178         } dat1, dat2;
179
180
181         dat1.f = f;
182         dat2.b[0] = dat1.b[3];
183         dat2.b[1] = dat1.b[2];
184         dat2.b[2] = dat1.b[1];
185         dat2.b[3] = dat1.b[0];
186         return dat2.f;
187 }
188
189 #if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
190 float FloatNoSwap (float f)
191 {
192         return f;
193 }
194 #endif
195
196 /*
197 ==============================================================================
198
199                         MESSAGE IO FUNCTIONS
200
201 Handles byte ordering and avoids alignment errors
202 ==============================================================================
203 */
204
205 //
206 // writing functions
207 //
208
209 void MSG_WriteChar (sizebuf_t *sb, int c)
210 {
211         qbyte    *buf;
212         
213         buf = SZ_GetSpace (sb, 1);
214         buf[0] = c;
215 }
216
217 void MSG_WriteByte (sizebuf_t *sb, int c)
218 {
219         qbyte    *buf;
220         
221         buf = SZ_GetSpace (sb, 1);
222         buf[0] = c;
223 }
224
225 void MSG_WriteShort (sizebuf_t *sb, int c)
226 {
227         qbyte    *buf;
228
229         buf = SZ_GetSpace (sb, 2);
230         buf[0] = c&0xff;
231         buf[1] = c>>8;
232 }
233
234 void MSG_WriteLong (sizebuf_t *sb, int c)
235 {
236         qbyte    *buf;
237
238         buf = SZ_GetSpace (sb, 4);
239         buf[0] = c&0xff;
240         buf[1] = (c>>8)&0xff;
241         buf[2] = (c>>16)&0xff;
242         buf[3] = c>>24;
243 }
244
245 void MSG_WriteFloat (sizebuf_t *sb, float f)
246 {
247         union
248         {
249                 float   f;
250                 int     l;
251         } dat;
252
253
254         dat.f = f;
255         dat.l = LittleLong (dat.l);
256
257         SZ_Write (sb, &dat.l, 4);
258 }
259
260 void MSG_WriteString (sizebuf_t *sb, char *s)
261 {
262         if (!s)
263                 SZ_Write (sb, "", 1);
264         else
265                 SZ_Write (sb, s, strlen(s)+1);
266 }
267
268 // used by server (always latest dpprotocol)
269 void MSG_WriteDPCoord (sizebuf_t *sb, float f)
270 {
271         if (f >= 0)
272                 MSG_WriteShort (sb, (int)(f + 0.5f));
273         else
274                 MSG_WriteShort (sb, (int)(f - 0.5f));
275 }
276
277 void MSG_WritePreciseAngle (sizebuf_t *sb, float f)
278 {
279         if (f >= 0)
280                 MSG_WriteShort (sb, (int)(f*(65536.0f/360.0f) + 0.5f) & 65535);
281         else
282                 MSG_WriteShort (sb, (int)(f*(65536.0f/360.0f) - 0.5f) & 65535);
283 }
284
285 // LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem
286 void MSG_WriteAngle (sizebuf_t *sb, float f)
287 {
288         if (f >= 0)
289                 MSG_WriteByte (sb, (int)(f*(256.0f/360.0f) + 0.5f) & 255);
290         else
291                 MSG_WriteByte (sb, (int)(f*(256.0f/360.0f) - 0.5f) & 255);
292 }
293
294 //
295 // reading functions
296 //
297 int                     msg_readcount;
298 qboolean        msg_badread;
299
300 void MSG_BeginReading (void)
301 {
302         msg_readcount = 0;
303         msg_badread = false;
304 }
305
306 int MSG_ReadShort (void)
307 {
308         int     c;
309
310         if (msg_readcount+2 > net_message.cursize)
311         {
312                 msg_badread = true;
313                 return -1;
314         }
315
316         c = (short)(net_message.data[msg_readcount]
317         + (net_message.data[msg_readcount+1]<<8));
318
319         msg_readcount += 2;
320
321         return c;
322 }
323
324 int MSG_ReadLong (void)
325 {
326         int     c;
327
328         if (msg_readcount+4 > net_message.cursize)
329         {
330                 msg_badread = true;
331                 return -1;
332         }
333
334         c = net_message.data[msg_readcount]
335         + (net_message.data[msg_readcount+1]<<8)
336         + (net_message.data[msg_readcount+2]<<16)
337         + (net_message.data[msg_readcount+3]<<24);
338
339         msg_readcount += 4;
340
341         return c;
342 }
343
344 float MSG_ReadFloat (void)
345 {
346         union
347         {
348                 qbyte    b[4];
349                 float   f;
350                 int     l;
351         } dat;
352
353         dat.b[0] =      net_message.data[msg_readcount];
354         dat.b[1] =      net_message.data[msg_readcount+1];
355         dat.b[2] =      net_message.data[msg_readcount+2];
356         dat.b[3] =      net_message.data[msg_readcount+3];
357         msg_readcount += 4;
358
359         dat.l = LittleLong (dat.l);
360
361         return dat.f;
362 }
363
364 char *MSG_ReadString (void)
365 {
366         static char     string[2048];
367         int             l,c;
368
369         l = 0;
370         do
371         {
372                 c = MSG_ReadChar ();
373                 if (c == -1 || c == 0)
374                         break;
375                 string[l] = c;
376                 l++;
377         } while (l < sizeof(string)-1);
378
379         string[l] = 0;
380
381         return string;
382 }
383
384 // used by server (always latest dpprotocol)
385 float MSG_ReadDPCoord (void)
386 {
387         return (signed short) MSG_ReadShort();
388 }
389
390 // used by client
391 float MSG_ReadCoord (void)
392 {
393         if (dpprotocol == DPPROTOCOL_VERSION2 || dpprotocol == DPPROTOCOL_VERSION3)
394                 return (signed short) MSG_ReadShort();
395         else if (dpprotocol == DPPROTOCOL_VERSION1)
396                 return MSG_ReadFloat();
397         else
398                 return MSG_ReadShort() * (1.0f/8.0f);
399 }
400
401
402 //===========================================================================
403
404 void SZ_Alloc (sizebuf_t *buf, int startsize, char *name)
405 {
406         if (startsize < 256)
407                 startsize = 256;
408         buf->mempool = Mem_AllocPool(name);
409         buf->data = Mem_Alloc(buf->mempool, startsize);
410         buf->maxsize = startsize;
411         buf->cursize = 0;
412 }
413
414
415 void SZ_Free (sizebuf_t *buf)
416 {
417         Mem_FreePool(&buf->mempool);
418         buf->data = NULL;
419         buf->maxsize = 0;
420         buf->cursize = 0;
421 }
422
423 void SZ_Clear (sizebuf_t *buf)
424 {
425         buf->cursize = 0;
426 }
427
428 void *SZ_GetSpace (sizebuf_t *buf, int length)
429 {
430         void    *data;
431
432         if (buf->cursize + length > buf->maxsize)
433         {
434                 if (!buf->allowoverflow)
435                         Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
436
437                 if (length > buf->maxsize)
438                         Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
439
440                 buf->overflowed = true;
441                 Con_Printf ("SZ_GetSpace: overflow");
442                 SZ_Clear (buf);
443         }
444
445         data = buf->data + buf->cursize;
446         buf->cursize += length;
447         
448         return data;
449 }
450
451 void SZ_Write (sizebuf_t *buf, void *data, int length)
452 {
453         memcpy (SZ_GetSpace(buf,length),data,length);         
454 }
455
456 void SZ_Print (sizebuf_t *buf, char *data)
457 {
458         int             len;
459         
460         len = strlen(data)+1;
461
462 // byte * cast to keep VC++ happy
463         if (buf->data[buf->cursize-1])
464                 memcpy ((qbyte *)SZ_GetSpace(buf, len),data,len); // no trailing 0
465         else
466                 memcpy ((qbyte *)SZ_GetSpace(buf, len-1)-1,data,len); // write over trailing 0
467 }
468
469
470 //============================================================================
471
472
473 /*
474 ============
475 COM_SkipPath
476 ============
477 */
478 char *COM_SkipPath (char *pathname)
479 {
480         char    *last;
481
482         last = pathname;
483         while (*pathname)
484         {
485                 if (*pathname=='/')
486                         last = pathname+1;
487                 pathname++;
488         }
489         return last;
490 }
491
492 /*
493 ============
494 COM_StripExtension
495 ============
496 */
497 // LordHavoc: replacement for severely broken COM_StripExtension that was used in original quake.
498 void COM_StripExtension (char *in, char *out)
499 {
500         char *last = NULL;
501         while (*in)
502         {
503                 if (*in == '.')
504                         last = out;
505                 else if (*in == '/' || *in == '\\' || *in == ':')
506                         last = NULL;
507                 *out++ = *in++;
508         }
509         if (last)
510                 *last = 0;
511 }
512
513 /*
514 ============
515 COM_FileExtension
516 ============
517 */
518 char *COM_FileExtension (char *in)
519 {
520         static char exten[8];
521         int             i;
522
523         while (*in && *in != '.')
524                 in++;
525         if (!*in)
526                 return "";
527         in++;
528         for (i=0 ; i<7 && *in ; i++,in++)
529                 exten[i] = *in;
530         exten[i] = 0;
531         return exten;
532 }
533
534 /*
535 ============
536 COM_FileBase
537 ============
538 */
539 void COM_FileBase (char *in, char *out)
540 {
541         char *slash, *dot;
542         char *s;
543
544         slash = in;
545         dot = NULL;
546         s = in;
547         while(*s)
548         {
549                 if (*s == '/')
550                         slash = s + 1;
551                 if (*s == '.')
552                         dot = s;
553                 s++;
554         }
555         if (dot == NULL)
556                 dot = s;
557         if (dot - slash < 2)
558                 strcpy (out,"?model?");
559         else
560         {
561                 while (slash < dot)
562                         *out++ = *slash++;
563                 *out++ = 0;
564         }
565 }
566
567
568 /*
569 ==================
570 COM_DefaultExtension
571 ==================
572 */
573 void COM_DefaultExtension (char *path, char *extension)
574 {
575         char    *src;
576 //
577 // if path doesn't have a .EXT, append extension
578 // (extension should include the .)
579 //
580         src = path + strlen(path) - 1;
581
582         while (*src != '/' && src != path)
583         {
584                 if (*src == '.')
585                         return;                 // it has an extension
586                 src--;
587         }
588
589         strcat (path, extension);
590 }
591
592
593 /*
594 ==============
595 COM_Parse
596
597 Parse a token out of a string
598 ==============
599 */
600 char *COM_Parse (char *data)
601 {
602         int             c;
603         int             len;
604         
605         len = 0;
606         com_token[0] = 0;
607         
608         if (!data)
609                 return NULL;
610                 
611 // skip whitespace
612 skipwhite:
613         while ( (c = *data) <= ' ')
614         {
615                 if (c == 0)
616                         return NULL;                    // end of file;
617                 data++;
618         }
619         
620 // skip // comments
621         if (c=='/' && data[1] == '/')
622         {
623                 while (*data && *data != '\n')
624                         data++;
625                 goto skipwhite;
626         }
627         
628
629 // handle quoted strings specially
630         if (c == '\"')
631         {
632                 data++;
633                 while (1)
634                 {
635                         c = *data++;
636                         if (c=='\"' || !c)
637                         {
638                                 com_token[len] = 0;
639                                 return data;
640                         }
641                         com_token[len] = c;
642                         len++;
643                 }
644         }
645
646 // parse single characters
647         if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
648         {
649                 com_token[len] = c;
650                 len++;
651                 com_token[len] = 0;
652                 return data+1;
653         }
654
655 // parse a regular word
656         do
657         {
658                 com_token[len] = c;
659                 data++;
660                 len++;
661                 c = *data;
662         if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
663                         break;
664         } while (c>32);
665         
666         com_token[len] = 0;
667         return data;
668 }
669
670
671 /*
672 ================
673 COM_CheckParm
674
675 Returns the position (1 to argc-1) in the program's argument list
676 where the given parameter apears, or 0 if not present
677 ================
678 */
679 int COM_CheckParm (char *parm)
680 {
681         int             i;
682         
683         for (i=1 ; i<com_argc ; i++)
684         {
685                 if (!com_argv[i])
686                         continue;               // NEXTSTEP sometimes clears appkit vars.
687                 if (!strcmp (parm,com_argv[i]))
688                         return i;
689         }
690                 
691         return 0;
692 }
693
694 /*
695 ================
696 COM_CheckRegistered
697
698 Looks for the pop.txt file and verifies it.
699 Sets the "registered" cvar.
700 Immediately exits out if an alternate game was attempted to be started without
701 being registered.
702 ================
703 */
704 void COM_CheckRegistered (void)
705 {
706         Cvar_Set ("cmdline", com_cmdline);
707
708         if (!Sys_FileTime("gfx/pop.lmp"))
709         {
710                 if (com_modified)
711                         Con_Printf ("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
712                 else
713                         Con_Printf ("Playing shareware version.\n");
714                 return;
715         }
716
717         Cvar_Set ("registered", "1");
718         Con_Printf ("Playing registered version.\n");
719 }
720
721
722 void COM_Path_f (void);
723
724
725 /*
726 ================
727 COM_InitArgv
728 ================
729 */
730 void COM_InitArgv (void)
731 {
732         int i, j, n;
733         // reconstitute the command line for the cmdline externally visible cvar
734         n = 0;
735         for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++)
736         {
737                 i = 0;
738                 while ((n < (CMDLINE_LENGTH - 1)) && com_argv[j][i])
739                         com_cmdline[n++] = com_argv[j][i++];
740                 if (n < (CMDLINE_LENGTH - 1))
741                         com_cmdline[n++] = ' ';
742                 else
743                         break;
744         }
745         com_cmdline[n] = 0;
746 }
747
748 void COM_InitGameType (void)
749 {
750         char name[128];
751         COM_StripExtension(com_argv[0], name);
752         COM_ToLowerString(name, name);
753
754         if (strstr(name, "transfusion"))
755                 gamemode = GAME_TRANSFUSION;
756         else if (strstr(name, "zymotic"))
757                 gamemode = GAME_ZYMOTIC;
758         else if (strstr(name, "fiendarena"))
759                 gamemode = GAME_FIENDARENA;
760         else if (strstr(name, "nehahra"))
761                 gamemode = GAME_NEHAHRA;
762         else if (strstr(name, "hipnotic"))
763                 gamemode = GAME_HIPNOTIC;
764         else if (strstr(name, "rogue"))
765                 gamemode = GAME_ROGUE;
766         else
767                 gamemode = GAME_NORMAL;
768
769         if (COM_CheckParm ("-transfusion"))
770                 gamemode = GAME_TRANSFUSION;
771         else if (COM_CheckParm ("-zymotic"))
772                 gamemode = GAME_ZYMOTIC;
773         else if (COM_CheckParm ("-fiendarena"))
774                 gamemode = GAME_FIENDARENA;
775         else if (COM_CheckParm ("-nehahra"))
776                 gamemode = GAME_NEHAHRA;
777         else if (COM_CheckParm ("-hipnotic"))
778                 gamemode = GAME_HIPNOTIC;
779         else if (COM_CheckParm ("-rogue"))
780                 gamemode = GAME_ROGUE;
781         else if (COM_CheckParm ("-quake"))
782                 gamemode = GAME_NORMAL;
783
784         switch(gamemode)
785         {
786         case GAME_NORMAL:
787                 if (registered.integer)
788                         gamename = "DarkPlaces-Quake";
789                 else
790                         gamename = "DarkPlaces-SharewareQuake";
791                 break;
792         case GAME_HIPNOTIC:
793                 gamename = "Darkplaces-Hipnotic";
794                 break;
795         case GAME_ROGUE:
796                 gamename = "Darkplaces-Rogue";
797                 break;
798         case GAME_NEHAHRA:
799                 gamename = "DarkPlaces-Nehahra";
800                 break;
801         case GAME_FIENDARENA:
802                 gamename = "FiendArena";
803                 break;
804         case GAME_ZYMOTIC:
805                 gamename = "Zymotic";
806                 break;
807         case GAME_TRANSFUSION:
808                 gamename = "Transfusion";
809                 break;
810         default:
811                 Sys_Error("COM_InitGameType: unknown gamemode %i\n", gamemode);
812                 break;
813         }
814 }
815
816
817 extern void Mathlib_Init(void);
818
819 /*
820 ================
821 COM_Init
822 ================
823 */
824 void COM_Init (void)
825 {
826 #if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
827         qbyte    swaptest[2] = {1,0};
828
829 // set the byte swapping variables in a portable manner
830         if ( *(short *)swaptest == 1)
831         {
832                 BigShort = ShortSwap;
833                 LittleShort = ShortNoSwap;
834                 BigLong = LongSwap;
835                 LittleLong = LongNoSwap;
836                 BigFloat = FloatSwap;
837                 LittleFloat = FloatNoSwap;
838         }
839         else
840         {
841                 BigShort = ShortNoSwap;
842                 LittleShort = ShortSwap;
843                 BigLong = LongNoSwap;
844                 LittleLong = LongSwap;
845                 BigFloat = FloatNoSwap;
846                 LittleFloat = FloatSwap;
847         }
848 #endif
849
850         pak_mempool = Mem_AllocPool("paks");
851
852         Cvar_RegisterVariable (&registered);
853         Cvar_RegisterVariable (&cmdline);
854         Cmd_AddCommand ("path", COM_Path_f);
855
856         Mathlib_Init();
857
858         COM_InitFilesystem ();
859         COM_CheckRegistered ();
860 }
861
862
863 /*
864 ============
865 va
866
867 does a varargs printf into a temp buffer, so I don't need to have
868 varargs versions of all text functions.
869 FIXME: make this buffer size safe someday
870 ============
871 */
872 char    *va(char *format, ...)
873 {
874         va_list argptr;
875         // LordHavoc: now cycles through 8 buffers to avoid problems in most cases
876         static char string[8][1024], *s;
877         static int stringindex = 0;
878
879         s = string[stringindex];
880         stringindex = (stringindex + 1) & 7;
881         va_start (argptr, format);
882         vsprintf (s, format,argptr);
883         va_end (argptr);
884
885         return s;
886 }
887
888
889 /*
890 =============================================================================
891
892 QUAKE FILESYSTEM
893
894 =============================================================================
895 */
896
897 int     com_filesize;
898
899
900 //
901 // in memory
902 //
903
904 typedef struct
905 {
906         char name[MAX_QPATH];
907         int filepos, filelen;
908 } packfile_t;
909
910 typedef struct pack_s
911 {
912         char filename[MAX_OSPATH];
913         int handle;
914         int numfiles;
915         packfile_t *files;
916         mempool_t *mempool;
917         struct pack_s *next;
918 } pack_t;
919
920 //
921 // on disk
922 //
923 typedef struct
924 {
925         char name[56];
926         int filepos, filelen;
927 } dpackfile_t;
928
929 typedef struct
930 {
931         char id[4];
932         int dirofs;
933         int dirlen;
934 } dpackheader_t;
935
936 // LordHavoc: was 2048, increased to 65536 and changed info[MAX_PACK_FILES] to a temporary alloc
937 #define MAX_FILES_IN_PACK       65536
938
939 pack_t  *packlist = NULL;
940
941 #if CACHEENABLE
942 char    com_cachedir[MAX_OSPATH];
943 #endif
944 char    com_gamedir[MAX_OSPATH];
945
946 typedef struct searchpath_s
947 {
948         char filename[MAX_OSPATH];
949         pack_t *pack;          // only one of filename / pack will be used
950         struct searchpath_s *next;
951 } searchpath_t;
952
953 searchpath_t    *com_searchpaths;
954
955 /*
956 ============
957 COM_Path_f
958
959 ============
960 */
961 void COM_Path_f (void)
962 {
963         searchpath_t    *s;
964
965         Con_Printf ("Current search path:\n");
966         for (s=com_searchpaths ; s ; s=s->next)
967         {
968                 if (s->pack)
969                 {
970                         Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
971                 }
972                 else
973                         Con_Printf ("%s\n", s->filename);
974         }
975 }
976
977 /*
978 ============
979 COM_CreatePath
980
981 LordHavoc: Previously only used for CopyFile, now also used for COM_WriteFile.
982 ============
983 */
984 void    COM_CreatePath (char *path)
985 {
986         char    *ofs, save;
987
988         for (ofs = path+1 ; *ofs ; ofs++)
989         {
990                 if (*ofs == '/' || *ofs == '\\')
991                 {
992                         // create the directory
993                         save = *ofs;
994                         *ofs = 0;
995                         Sys_mkdir (path);
996                         *ofs = save;
997                 }
998         }
999 }
1000
1001
1002 /*
1003 ============
1004 COM_WriteFile
1005
1006 The filename will be prefixed by the current game directory
1007 ============
1008 */
1009 qboolean COM_WriteFile (char *filename, void *data, int len)
1010 {
1011         int             handle;
1012         char    name[MAX_OSPATH];
1013
1014         sprintf (name, "%s/%s", com_gamedir, filename);
1015
1016         // LordHavoc: added this
1017         COM_CreatePath (name); // create directories up to the file
1018
1019         handle = Sys_FileOpenWrite (name);
1020         if (handle == -1)
1021         {
1022                 Con_Printf ("COM_WriteFile: failed on %s\n", name);
1023                 return false;
1024         }
1025
1026         Con_DPrintf ("COM_WriteFile: %s\n", name);
1027         Sys_FileWrite (handle, data, len);
1028         Sys_FileClose (handle);
1029         return true;
1030 }
1031
1032
1033 /*
1034 ===========
1035 COM_CopyFile
1036
1037 Copies a file over from the net to the local cache, creating any directories
1038 needed.  This is for the convenience of developers using ISDN from home.
1039 ===========
1040 */
1041 void COM_CopyFile (char *netpath, char *cachepath)
1042 {
1043         int             in, out;
1044         int             remaining, count;
1045         char    buf[4096];
1046
1047         remaining = Sys_FileOpenRead (netpath, &in);            
1048         COM_CreatePath (cachepath);     // create directories up to the cache file
1049         out = Sys_FileOpenWrite (cachepath);
1050         
1051         while (remaining)
1052         {
1053                 if (remaining < sizeof(buf))
1054                         count = remaining;
1055                 else
1056                         count = sizeof(buf);
1057                 Sys_FileRead (in, buf, count);
1058                 Sys_FileWrite (out, buf, count);
1059                 remaining -= count;
1060         }
1061
1062         Sys_FileClose (in);
1063         Sys_FileClose (out);    
1064 }
1065
1066 /*
1067 ===========
1068 COM_OpenRead
1069 ===========
1070 */
1071 QFile * COM_OpenRead (const char *path, int offs, int len, qboolean zip)
1072 {
1073         int                             fd = open (path, O_RDONLY);
1074         unsigned char   id[2];
1075         unsigned char   len_bytes[4];
1076
1077         if (fd == -1)
1078         {
1079                 Sys_Error ("Couldn't open %s", path);
1080                 return 0;
1081         }
1082         if (offs < 0 || len < 0)
1083         {
1084                 // normal file
1085                 offs = 0;
1086                 len = lseek (fd, 0, SEEK_END);
1087                 lseek (fd, 0, SEEK_SET);
1088         }
1089         lseek (fd, offs, SEEK_SET);
1090         if (zip)
1091         {
1092                 read (fd, id, 2);
1093                 if (id[0] == 0x1f && id[1] == 0x8b)
1094                 {
1095                         lseek (fd, offs + len - 4, SEEK_SET);
1096                         read (fd, len_bytes, 4);
1097                         len = ((len_bytes[3] << 24)
1098                                    | (len_bytes[2] << 16)
1099                                    | (len_bytes[1] << 8)
1100                                    | (len_bytes[0]));
1101                 }
1102         }
1103         lseek (fd, offs, SEEK_SET);
1104         com_filesize = len;
1105
1106 #ifdef WIN32
1107         setmode (fd, O_BINARY);
1108 #endif
1109         if (zip)
1110                 return Qdopen (fd, "rbz");
1111         else
1112                 return Qdopen (fd, "rb");
1113 }
1114
1115 /*
1116 ===========
1117 COM_FindFile
1118
1119 Finds the file in the search path.
1120 Sets com_filesize and one of handle or file
1121 ===========
1122 */
1123 int COM_FindFile (char *filename, QFile **file, qboolean quiet, qboolean zip)
1124 {
1125         searchpath_t    *search;
1126         char                    netpath[MAX_OSPATH];
1127 #if CACHEENABLE
1128         char                    cachepath[MAX_OSPATH];
1129         int                             cachetime;
1130 #endif
1131         pack_t                  *pak;
1132         int                             i;
1133         int                             findtime;
1134         char                    gzfilename[MAX_OSPATH];
1135         int                             filenamelen;
1136
1137         filenamelen = strlen (filename);
1138         sprintf (gzfilename, "%s.gz", filename);
1139
1140         if (!file)
1141                 Sys_Error ("COM_FindFile: file not set");
1142                 
1143 //
1144 // search through the path, one element at a time
1145 //
1146         search = com_searchpaths;
1147
1148         for ( ; search ; search = search->next)
1149         {
1150         // is the element a pak file?
1151                 if (search->pack)
1152                 {
1153                 // look through all the pak file elements
1154                         pak = search->pack;
1155                         for (i=0 ; i<pak->numfiles ; i++)
1156                                 if (!strcmp (pak->files[i].name, filename)
1157                                     || !strcmp (pak->files[i].name, gzfilename))
1158                                 {       // found it!
1159                                         if (!quiet)
1160                                                 Sys_Printf ("PackFile: %s : %s\n",pak->filename, pak->files[i].name);
1161                                         // open a new file on the pakfile
1162                                         *file = COM_OpenRead (pak->filename, pak->files[i].filepos, pak->files[i].filelen, zip);
1163                                         return com_filesize;
1164                                 }
1165                 }
1166                 else
1167                 {               
1168                         sprintf (netpath, "%s/%s",search->filename, filename);
1169                         
1170                         findtime = Sys_FileTime (netpath);
1171                         if (findtime == -1)
1172                                 continue;
1173                                 
1174 #if CACHEENABLE
1175                         // see if the file needs to be updated in the cache
1176                         if (com_cachedir[0])
1177                         {       
1178 #if defined(_WIN32)
1179                                 if ((strlen(netpath) < 2) || (netpath[1] != ':'))
1180                                         sprintf (cachepath,"%s%s", com_cachedir, netpath);
1181                                 else
1182                                         sprintf (cachepath,"%s%s", com_cachedir, netpath+2);
1183 #else
1184                                 sprintf (cachepath,"%s%s", com_cachedir, netpath);
1185 #endif
1186
1187                                 cachetime = Sys_FileTime (cachepath);
1188                         
1189                                 if (cachetime < findtime)
1190                                         COM_CopyFile (netpath, cachepath);
1191                                 strcpy (netpath, cachepath);
1192                         }       
1193 #endif
1194
1195                         if (!quiet)
1196                                 Sys_Printf ("FindFile: %s\n",netpath);
1197                         *file = COM_OpenRead (netpath, -1, -1, zip);
1198                         return com_filesize;
1199                 }
1200                 
1201         }
1202         
1203         if (!quiet)
1204                 Sys_Printf ("FindFile: can't find %s\n", filename);
1205         
1206         *file = NULL;
1207         com_filesize = -1;
1208         return -1;
1209 }
1210
1211
1212 /*
1213 ===========
1214 COM_FOpenFile
1215
1216 If the requested file is inside a packfile, a new QFile * will be opened
1217 into the file.
1218 ===========
1219 */
1220 int COM_FOpenFile (char *filename, QFile **file, qboolean quiet, qboolean zip)
1221 {
1222         return COM_FindFile (filename, file, quiet, zip);
1223 }
1224
1225
1226 /*
1227 ============
1228 COM_LoadFile
1229
1230 Filename are reletive to the quake directory.
1231 Always appends a 0 byte.
1232 ============
1233 */
1234 qbyte *loadbuf;
1235 int loadsize;
1236 qbyte *COM_LoadFile (char *path, qboolean quiet)
1237 {
1238         QFile *h;
1239         qbyte *buf;
1240         char base[1024];
1241         int len;
1242
1243         buf = NULL;     // quiet compiler warning
1244         loadsize = 0;
1245
1246 // look for it in the filesystem or pack files
1247         len = COM_FOpenFile (path, &h, quiet, true);
1248         if (!h)
1249                 return NULL;
1250
1251         loadsize = len;
1252
1253 // extract the filename base name for hunk tag
1254         COM_FileBase (path, base);
1255
1256         buf = Mem_Alloc(tempmempool, len+1);
1257         if (!buf)
1258                 Sys_Error ("COM_LoadFile: not enough available memory for %s (size %i)", path, len);
1259
1260         ((qbyte *)buf)[len] = 0;
1261
1262         Qread (h, buf, len);
1263         Qclose (h);
1264
1265         return buf;
1266 }
1267
1268 /*
1269 =================
1270 COM_LoadPackFile
1271
1272 Takes an explicit (not game tree related) path to a pak file.
1273
1274 Loads the header and directory, adding the files at the beginning
1275 of the list so they override previous pack files.
1276 =================
1277 */
1278 pack_t *COM_LoadPackFile (char *packfile)
1279 {
1280         dpackheader_t   header;
1281         int                             i;
1282         int                             numpackfiles;
1283         pack_t                  *pack;
1284         int                             packhandle;
1285         // LordHavoc: changed from stack array to temporary alloc, allowing huge pack directories
1286         dpackfile_t             *info;
1287
1288         if (Sys_FileOpenRead (packfile, &packhandle) == -1)
1289                 return NULL;
1290
1291         Sys_FileRead (packhandle, (void *)&header, sizeof(header));
1292         if (memcmp(header.id, "PACK", 4))
1293                 Sys_Error ("%s is not a packfile", packfile);
1294         header.dirofs = LittleLong (header.dirofs);
1295         header.dirlen = LittleLong (header.dirlen);
1296
1297         if (header.dirlen % sizeof(dpackfile_t))
1298                 Sys_Error ("%s has an invalid directory size", packfile);
1299
1300         numpackfiles = header.dirlen / sizeof(dpackfile_t);
1301
1302         if (numpackfiles > MAX_FILES_IN_PACK)
1303                 Sys_Error ("%s has %i files", packfile, numpackfiles);
1304
1305         pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
1306         strcpy (pack->filename, packfile);
1307         pack->handle = packhandle;
1308         pack->numfiles = numpackfiles;
1309         pack->mempool = Mem_AllocPool(packfile);
1310         pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
1311         pack->next = packlist;
1312         packlist = pack;
1313
1314         info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
1315         Sys_FileSeek (packhandle, header.dirofs);
1316         Sys_FileRead (packhandle, (void *)info, header.dirlen);
1317
1318 // parse the directory
1319         for (i = 0;i < numpackfiles;i++)
1320         {
1321                 strcpy (pack->files[i].name, info[i].name);
1322                 pack->files[i].filepos = LittleLong(info[i].filepos);
1323                 pack->files[i].filelen = LittleLong(info[i].filelen);
1324         }
1325
1326         Mem_Free(info);
1327
1328         Con_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
1329         return pack;
1330 }
1331
1332
1333 /*
1334 ================
1335 COM_AddGameDirectory
1336
1337 Sets com_gamedir, adds the directory to the head of the path,
1338 then loads and adds pak1.pak pak2.pak ...
1339 ================
1340 */
1341 void COM_AddGameDirectory (char *dir)
1342 {
1343         stringlist_t *list, *current;
1344         searchpath_t *search;
1345         pack_t *pak;
1346         char pakfile[MAX_OSPATH];
1347
1348         strcpy (com_gamedir, dir);
1349
1350 //
1351 // add the directory to the search path
1352 //
1353         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
1354         strcpy (search->filename, dir);
1355         search->next = com_searchpaths;
1356         com_searchpaths = search;
1357
1358         // add any paks in the directory
1359         list = listdirectory(dir);
1360         for (current = list;current;current = current->next)
1361         {
1362                 if (matchpattern(current->text, "*.pak"))
1363                 {
1364                         sprintf (pakfile, "%s/%s", dir, current->text);
1365                         pak = COM_LoadPackFile (pakfile);
1366                         if (pak)
1367                         {
1368                                 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
1369                                 search->pack = pak;
1370                                 search->next = com_searchpaths;
1371                                 com_searchpaths = search;
1372                         }
1373                         else
1374                                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1375                 }
1376         }
1377         freedirectory(list);
1378 }
1379
1380 /*
1381 ================
1382 COM_InitFilesystem
1383 ================
1384 */
1385 void COM_InitFilesystem (void)
1386 {
1387         int             i, j;
1388         char    basedir[MAX_OSPATH];
1389         searchpath_t    *search;
1390
1391 //
1392 // -basedir <path>
1393 // Overrides the system supplied base directory (under GAMENAME)
1394 //
1395         i = COM_CheckParm ("-basedir");
1396         if (i && i < com_argc-1)
1397                 strcpy (basedir, com_argv[i+1]);
1398         else
1399                 strcpy (basedir, host_parms.basedir);
1400
1401         j = strlen (basedir);
1402
1403         if (j > 0)
1404         {
1405                 if ((basedir[j-1] == '\\') || (basedir[j-1] == '/'))
1406                         basedir[j-1] = 0;
1407         }
1408
1409 #if CACHEENABLE
1410 //
1411 // -cachedir <path>
1412 // Overrides the system supplied cache directory (NULL or /qcache)
1413 // -cachedir - will disable caching.
1414 //
1415         i = COM_CheckParm ("-cachedir");
1416         if (i && i < com_argc-1)
1417         {
1418                 if (com_argv[i+1][0] == '-')
1419                         com_cachedir[0] = 0;
1420                 else
1421                         strcpy (com_cachedir, com_argv[i+1]);
1422         }
1423         else if (host_parms.cachedir)
1424                 strcpy (com_cachedir, host_parms.cachedir);
1425         else
1426                 com_cachedir[0] = 0;
1427 #endif
1428
1429 // start up with GAMENAME by default (id1)
1430         COM_AddGameDirectory (va("%s/"GAMENAME, basedir) );
1431
1432         switch(gamemode)
1433         {
1434         case GAME_NORMAL:
1435                 break;
1436         case GAME_HIPNOTIC:
1437                 COM_AddGameDirectory (va("%s/hipnotic", basedir) );
1438                 break;
1439         case GAME_ROGUE:
1440                 COM_AddGameDirectory (va("%s/rogue", basedir) );
1441                 break;
1442         case GAME_NEHAHRA:
1443                 COM_AddGameDirectory (va("%s/nehahra", basedir) );
1444                 break;
1445         case GAME_FIENDARENA:
1446                 COM_AddGameDirectory (va("%s/fiendarena", basedir) );
1447                 break;
1448         case GAME_ZYMOTIC:
1449                 COM_AddGameDirectory (va("%s/zymotic", basedir) );
1450                 break;
1451         case GAME_TRANSFUSION:
1452                 COM_AddGameDirectory (va("%s/transfusion", basedir) );
1453                 break;
1454         default:
1455                 Sys_Error("COM_InitFilesystem: unknown gamemode %i\n", gamemode);
1456                 break;
1457         }
1458
1459 //
1460 // -game <gamedir>
1461 // Adds basedir/gamedir as an override game
1462 //
1463         i = COM_CheckParm ("-game");
1464         if (i && i < com_argc-1)
1465         {
1466                 com_modified = true;
1467                 COM_AddGameDirectory (va("%s/%s", basedir, com_argv[i+1]));
1468         }
1469
1470 //
1471 // -path <dir or packfile> [<dir or packfile>] ...
1472 // Fully specifies the exact search path, overriding the generated one
1473 //
1474         i = COM_CheckParm ("-path");
1475         if (i)
1476         {
1477                 com_modified = true;
1478                 com_searchpaths = NULL;
1479                 while (++i < com_argc)
1480                 {
1481                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1482                                 break;
1483
1484                         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
1485                         if ( !strcmp(COM_FileExtension(com_argv[i]), "pak") )
1486                         {
1487                                 search->pack = COM_LoadPackFile (com_argv[i]);
1488                                 if (!search->pack)
1489                                         Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
1490                         }
1491                         else
1492                                 strcpy (search->filename, com_argv[i]);
1493                         search->next = com_searchpaths;
1494                         com_searchpaths = search;
1495                 }
1496         }
1497 }
1498
1499 int COM_FileExists(char *filename)
1500 {
1501         searchpath_t    *search;
1502         char                    netpath[MAX_OSPATH];
1503         pack_t                  *pak;
1504         int                             i;
1505         int                             findtime;
1506
1507         for (search = com_searchpaths;search;search = search->next)
1508         {
1509                 if (search->pack)
1510                 {
1511                         pak = search->pack;
1512                         for (i = 0;i < pak->numfiles;i++)
1513                                 if (!strcmp (pak->files[i].name, filename))
1514                                         return true;
1515                 }
1516                 else
1517                 {
1518                         sprintf (netpath, "%s/%s",search->filename, filename);
1519                         findtime = Sys_FileTime (netpath);
1520                         if (findtime != -1)
1521                                 return true;
1522                 }               
1523         }
1524
1525         return false;
1526 }
1527
1528
1529 //======================================
1530 // LordHavoc: added these because they are useful
1531
1532 void COM_ToLowerString(char *in, char *out)
1533 {
1534         while (*in)
1535         {
1536                 if (*in >= 'A' && *in <= 'Z')
1537                         *out++ = *in++ + 'a' - 'A';
1538                 else
1539                         *out++ = *in++;
1540         }
1541 }
1542
1543 void COM_ToUpperString(char *in, char *out)
1544 {
1545         while (*in)
1546         {
1547                 if (*in >= 'a' && *in <= 'z')
1548                         *out++ = *in++ + 'A' - 'a';
1549                 else
1550                         *out++ = *in++;
1551         }
1552 }
1553
1554 int COM_StringBeginsWith(const char *s, const char *match)
1555 {
1556         for (;*s && *match;s++, match++)
1557                 if (*s != *match)
1558                         return false;
1559         return true;
1560 }
1561