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