1 /*
2 Copyright (C) 1996-2001 Id Software, Inc.
3 Copyright (C) 2002-2009 John Fitzgibbons and others
4 Copyright (C) 2010-2014 QuakeSpasm developers
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21 */
22
23 // common.c -- misc functions used in client and server
24
25 #include "quakedef.h"
26 #include "q_ctype.h"
27 #include <errno.h>
28
29 #include "miniz.h"
30
31 static char *largv[MAX_NUM_ARGVS + 1];
32 static char argvdummy[] = " ";
33
34 int safemode;
35
36 cvar_t registered = {"registered","1",CVAR_ROM}; /* set to correct value in COM_CheckRegistered() */
37 cvar_t cmdline = {"cmdline","",CVAR_ROM/*|CVAR_SERVERINFO*/}; /* sending cmdline upon CCREQ_RULE_INFO is evil */
38
39 static qboolean com_modified; // set true if using non-id files
40
41 qboolean fitzmode;
42
43 static void COM_Path_f (void);
44
45 // if a packfile directory differs from this, it is assumed to be hacked
46 #define PAK0_COUNT 339 /* id1/pak0.pak - v1.0x */
47 #define PAK0_CRC_V100 13900 /* id1/pak0.pak - v1.00 */
48 #define PAK0_CRC_V101 62751 /* id1/pak0.pak - v1.01 */
49 #define PAK0_CRC_V106 32981 /* id1/pak0.pak - v1.06 */
50 #define PAK0_CRC (PAK0_CRC_V106)
51 #define PAK0_COUNT_V091 308 /* id1/pak0.pak - v0.91/0.92, not supported */
52 #define PAK0_CRC_V091 28804 /* id1/pak0.pak - v0.91/0.92, not supported */
53
54 char com_token[1024];
55 int com_argc;
56 char **com_argv;
57
58 #define CMDLINE_LENGTH 256 /* johnfitz -- mirrored in cmd.c */
59 char com_cmdline[CMDLINE_LENGTH];
60
61 qboolean standard_quake = true, rogue, hipnotic;
62
63 // this graphic needs to be in the pak file to use registered features
64 static unsigned short pop[] =
65 {
66 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
67 0x0000,0x0000,0x6600,0x0000,0x0000,0x0000,0x6600,0x0000,
68 0x0000,0x0066,0x0000,0x0000,0x0000,0x0000,0x0067,0x0000,
69 0x0000,0x6665,0x0000,0x0000,0x0000,0x0000,0x0065,0x6600,
70 0x0063,0x6561,0x0000,0x0000,0x0000,0x0000,0x0061,0x6563,
71 0x0064,0x6561,0x0000,0x0000,0x0000,0x0000,0x0061,0x6564,
72 0x0064,0x6564,0x0000,0x6469,0x6969,0x6400,0x0064,0x6564,
73 0x0063,0x6568,0x6200,0x0064,0x6864,0x0000,0x6268,0x6563,
74 0x0000,0x6567,0x6963,0x0064,0x6764,0x0063,0x6967,0x6500,
75 0x0000,0x6266,0x6769,0x6a68,0x6768,0x6a69,0x6766,0x6200,
76 0x0000,0x0062,0x6566,0x6666,0x6666,0x6666,0x6562,0x0000,
77 0x0000,0x0000,0x0062,0x6364,0x6664,0x6362,0x0000,0x0000,
78 0x0000,0x0000,0x0000,0x0062,0x6662,0x0000,0x0000,0x0000,
79 0x0000,0x0000,0x0000,0x0061,0x6661,0x0000,0x0000,0x0000,
80 0x0000,0x0000,0x0000,0x0000,0x6500,0x0000,0x0000,0x0000,
81 0x0000,0x0000,0x0000,0x0000,0x6400,0x0000,0x0000,0x0000
82 };
83
84 /*
85
86 All of Quake's data access is through a hierchal file system, but the contents
87 of the file system can be transparently merged from several sources.
88
89 The "base directory" is the path to the directory holding the quake.exe and all
90 game directories. The sys_* files pass this to host_init in quakeparms_t->basedir.
91 This can be overridden with the "-basedir" command line parm to allow code
92 debugging in a different directory. The base directory is only used during
93 filesystem initialization.
94
95 The "game directory" is the first tree on the search path and directory that all
96 generated files (savegames, screenshots, demos, config files) will be saved to.
97 This can be overridden with the "-game" command line parameter. The game
98 directory can never be changed while quake is executing. This is a precacution
99 against having a malicious server instruct clients to write files over areas they
100 shouldn't.
101
102 The "cache directory" is only used during development to save network bandwidth,
103 especially over ISDN / T1 lines. If there is a cache directory specified, when
104 a file is found by the normal search path, it will be mirrored into the cache
105 directory, then opened there.
106
107 FIXME:
108 The file "parms.txt" will be read out of the game directory and appended to the
109 current command line arguments to allow different games to initialize startup
110 parms differently. This could be used to add a "-sspeed 22050" for the high
111 quality sound edition. Because they are added at the end, they will not
112 override an explicit setting on the original command line.
113
114 */
115
116 //============================================================================
117
118
119 // ClearLink is used for new headnodes
ClearLink(link_t * l)120 void ClearLink (link_t *l)
121 {
122 l->prev = l->next = l;
123 }
124
RemoveLink(link_t * l)125 void RemoveLink (link_t *l)
126 {
127 l->next->prev = l->prev;
128 l->prev->next = l->next;
129 }
130
InsertLinkBefore(link_t * l,link_t * before)131 void InsertLinkBefore (link_t *l, link_t *before)
132 {
133 l->next = before;
134 l->prev = before->prev;
135 l->prev->next = l;
136 l->next->prev = l;
137 }
138
InsertLinkAfter(link_t * l,link_t * after)139 void InsertLinkAfter (link_t *l, link_t *after)
140 {
141 l->next = after->next;
142 l->prev = after;
143 l->prev->next = l;
144 l->next->prev = l;
145 }
146
147 /*
148 ============================================================================
149
150 LIBRARY REPLACEMENT FUNCTIONS
151
152 ============================================================================
153 */
154
q_strcasecmp(const char * s1,const char * s2)155 int q_strcasecmp(const char * s1, const char * s2)
156 {
157 const char * p1 = s1;
158 const char * p2 = s2;
159 char c1, c2;
160
161 if (p1 == p2)
162 return 0;
163
164 do
165 {
166 c1 = q_tolower (*p1++);
167 c2 = q_tolower (*p2++);
168 if (c1 == '\0')
169 break;
170 } while (c1 == c2);
171
172 return (int)(c1 - c2);
173 }
174
q_strncasecmp(const char * s1,const char * s2,size_t n)175 int q_strncasecmp(const char *s1, const char *s2, size_t n)
176 {
177 const char * p1 = s1;
178 const char * p2 = s2;
179 char c1, c2;
180
181 if (p1 == p2 || n == 0)
182 return 0;
183
184 do
185 {
186 c1 = q_tolower (*p1++);
187 c2 = q_tolower (*p2++);
188 if (c1 == '\0' || c1 != c2)
189 break;
190 } while (--n > 0);
191
192 return (int)(c1 - c2);
193 }
194
195 //spike -- grabbed this from fte, because its useful to me
q_strcasestr(const char * haystack,const char * needle)196 char *q_strcasestr(const char *haystack, const char *needle)
197 {
198 int c1, c2, c2f;
199 int i;
200 c2f = *needle;
201 if (c2f >= 'a' && c2f <= 'z')
202 c2f -= ('a' - 'A');
203 if (!c2f)
204 return (char*)haystack;
205 while (1)
206 {
207 c1 = *haystack;
208 if (!c1)
209 return NULL;
210 if (c1 >= 'a' && c1 <= 'z')
211 c1 -= ('a' - 'A');
212 if (c1 == c2f)
213 {
214 for (i = 1; ; i++)
215 {
216 c1 = haystack[i];
217 c2 = needle[i];
218 if (c1 >= 'a' && c1 <= 'z')
219 c1 -= ('a' - 'A');
220 if (c2 >= 'a' && c2 <= 'z')
221 c2 -= ('a' - 'A');
222 if (!c2)
223 return (char*)haystack; //end of needle means we found a complete match
224 if (!c1) //end of haystack means we can't possibly find needle in it any more
225 return NULL;
226 if (c1 != c2) //mismatch means no match starting at haystack[0]
227 break;
228 }
229 }
230 haystack++;
231 }
232 return NULL; //didn't find it
233 }
234
q_strlwr(char * str)235 char *q_strlwr (char *str)
236 {
237 char *c;
238 c = str;
239 while (*c)
240 {
241 *c = q_tolower(*c);
242 c++;
243 }
244 return str;
245 }
246
q_strupr(char * str)247 char *q_strupr (char *str)
248 {
249 char *c;
250 c = str;
251 while (*c)
252 {
253 *c = q_toupper(*c);
254 c++;
255 }
256 return str;
257 }
258
259 /* platform dependant (v)snprintf function names: */
260 #if defined(_WIN32)
261 #define snprintf_func _snprintf
262 #define vsnprintf_func _vsnprintf
263 #else
264 #define snprintf_func snprintf
265 #define vsnprintf_func vsnprintf
266 #endif
267
q_vsnprintf(char * str,size_t size,const char * format,va_list args)268 int q_vsnprintf(char *str, size_t size, const char *format, va_list args)
269 {
270 int ret;
271
272 ret = vsnprintf_func (str, size, format, args);
273
274 if (ret < 0)
275 ret = (int)size;
276 if (size == 0) /* no buffer */
277 return ret;
278 if ((size_t)ret >= size)
279 str[size - 1] = '\0';
280
281 return ret;
282 }
283
q_snprintf(char * str,size_t size,const char * format,...)284 int q_snprintf (char *str, size_t size, const char *format, ...)
285 {
286 int ret;
287 va_list argptr;
288
289 va_start (argptr, format);
290 ret = q_vsnprintf (str, size, format, argptr);
291 va_end (argptr);
292
293 return ret;
294 }
295
Q_memset(void * dest,int fill,size_t count)296 void Q_memset (void *dest, int fill, size_t count)
297 {
298 size_t i;
299
300 if ( (((uintptr_t)dest | count) & 3) == 0)
301 {
302 count >>= 2;
303 uint32_t fill32 = (uint32_t)fill | ((uint32_t)fill<<8) | ((uint32_t)fill<<16) | ((uint32_t)fill<<24);;
304 for (i = 0; i < count; i++)
305 ((uint32_t *)dest)[i] = fill32;
306 }
307 else
308 for (i = 0; i < count; i++)
309 ((byte *)dest)[i] = fill;
310 }
311
Q_memcpy(void * dest,const void * src,size_t count)312 void Q_memcpy (void *dest, const void *src, size_t count)
313 {
314 size_t i;
315
316 if (( ( (uintptr_t)dest | (uintptr_t)src | count) & 3) == 0)
317 {
318 count >>= 2;
319 for (i = 0; i < count; i++)
320 ((int *)dest)[i] = ((int *)src)[i];
321 }
322 else
323 for (i = 0; i < count; i++)
324 ((byte *)dest)[i] = ((byte *)src)[i];
325 }
326
Q_memcmp(const void * m1,const void * m2,size_t count)327 int Q_memcmp (const void *m1, const void *m2, size_t count)
328 {
329 while(count)
330 {
331 count--;
332 if (((byte *)m1)[count] != ((byte *)m2)[count])
333 return -1;
334 }
335 return 0;
336 }
337
Q_strcpy(char * dest,const char * src)338 void Q_strcpy (char *dest, const char *src)
339 {
340 while (*src)
341 {
342 *dest++ = *src++;
343 }
344 *dest++ = 0;
345 }
346
Q_strncpy(char * dest,const char * src,int count)347 void Q_strncpy (char *dest, const char *src, int count)
348 {
349 while (*src && count--)
350 {
351 *dest++ = *src++;
352 }
353 if (count)
354 *dest++ = 0;
355 }
356
Q_strlen(const char * str)357 int Q_strlen (const char *str)
358 {
359 int count;
360
361 count = 0;
362 while (str[count])
363 count++;
364
365 return count;
366 }
367
Q_strrchr(const char * s,char c)368 char *Q_strrchr(const char *s, char c)
369 {
370 int len = Q_strlen(s);
371 s += len;
372 while (len--)
373 {
374 if (*--s == c)
375 return (char *)s;
376 }
377 return NULL;
378 }
379
Q_strcat(char * dest,const char * src)380 void Q_strcat (char *dest, const char *src)
381 {
382 dest += Q_strlen(dest);
383 Q_strcpy (dest, src);
384 }
385
Q_strcmp(const char * s1,const char * s2)386 int Q_strcmp (const char *s1, const char *s2)
387 {
388 while (1)
389 {
390 if (*s1 != *s2)
391 return -1; // strings not equal
392 if (!*s1)
393 return 0; // strings are equal
394 s1++;
395 s2++;
396 }
397
398 return -1;
399 }
400
Q_strncmp(const char * s1,const char * s2,int count)401 int Q_strncmp (const char *s1, const char *s2, int count)
402 {
403 while (1)
404 {
405 if (!count--)
406 return 0;
407 if (*s1 != *s2)
408 return -1; // strings not equal
409 if (!*s1)
410 return 0; // strings are equal
411 s1++;
412 s2++;
413 }
414
415 return -1;
416 }
417
Q_atoi(const char * str)418 int Q_atoi (const char *str)
419 {
420 int val;
421 int sign;
422 int c;
423
424 if (*str == '-')
425 {
426 sign = -1;
427 str++;
428 }
429 else
430 sign = 1;
431
432 val = 0;
433
434 //
435 // check for hex
436 //
437 if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
438 {
439 str += 2;
440 while (1)
441 {
442 c = *str++;
443 if (c >= '0' && c <= '9')
444 val = (val<<4) + c - '0';
445 else if (c >= 'a' && c <= 'f')
446 val = (val<<4) + c - 'a' + 10;
447 else if (c >= 'A' && c <= 'F')
448 val = (val<<4) + c - 'A' + 10;
449 else
450 return val*sign;
451 }
452 }
453
454 //
455 // check for character
456 //
457 if (str[0] == '\'')
458 {
459 return sign * str[1];
460 }
461
462 //
463 // assume decimal
464 //
465 while (1)
466 {
467 c = *str++;
468 if (c <'0' || c > '9')
469 return val*sign;
470 val = val*10 + c - '0';
471 }
472
473 return 0;
474 }
475
476
Q_atof(const char * str)477 float Q_atof (const char *str)
478 {
479 double val;
480 int sign;
481 int c;
482 int decimal, total;
483
484 if (*str == '-')
485 {
486 sign = -1;
487 str++;
488 }
489 else
490 sign = 1;
491
492 val = 0;
493
494 //
495 // check for hex
496 //
497 if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
498 {
499 str += 2;
500 while (1)
501 {
502 c = *str++;
503 if (c >= '0' && c <= '9')
504 val = (val*16) + c - '0';
505 else if (c >= 'a' && c <= 'f')
506 val = (val*16) + c - 'a' + 10;
507 else if (c >= 'A' && c <= 'F')
508 val = (val*16) + c - 'A' + 10;
509 else
510 return val*sign;
511 }
512 }
513
514 //
515 // check for character
516 //
517 if (str[0] == '\'')
518 {
519 return sign * str[1];
520 }
521
522 //
523 // assume decimal
524 //
525 decimal = -1;
526 total = 0;
527 while (1)
528 {
529 c = *str++;
530 if (c == '.')
531 {
532 decimal = total;
533 continue;
534 }
535 if (c <'0' || c > '9')
536 break;
537 val = val*10 + c - '0';
538 total++;
539 }
540
541 if (decimal == -1)
542 return val*sign;
543 while (total > decimal)
544 {
545 val /= 10;
546 total--;
547 }
548
549 return val*sign;
550 }
551
552 // Q_ftoa: convert IEEE 754 float to a base-10 string with "infinite" decimal places
Q_ftoa(char * str,float in)553 void Q_ftoa(char *str, float in)
554 {
555 struct {
556 float f;
557 unsigned int i;
558 } u = {in};
559
560 int signbit = (u.i & 0x80000000) >> 31;
561 int exp = (signed int)((u.i & 0x7F800000) >> 23) - 127;
562 int mantissa = (u.i & 0x007FFFFF);
563
564 if (exp == 128) // 255(NaN/Infinity bits) - 127(bias)
565 {
566 if (signbit)
567 {
568 *str = '-';
569 str++;
570 }
571 if (mantissa == 0) // infinity
572 strcpy(str, "1.#INF");
573 else // NaN or indeterminate
574 strcpy(str, "1.#NAN");
575 return;
576 }
577
578 exp = -exp;
579 exp = (int)(exp * 0.30102999957f); // convert base 2 to base 10
580 exp += 8;
581
582 if (exp <= 0)
583 sprintf(str, "%.0f", in);
584 else
585 {
586 char tstr[32];
587 char *lsig = str - 1;
588 sprintf(tstr, "%%.%if", exp);
589 sprintf(str, tstr, in);
590 // find last significant digit and trim
591 while (*str)
592 {
593 if (*str >= '1' && *str <= '9')
594 lsig = str;
595 else if (*str == '.')
596 lsig = str - 1;
597 str++;
598 }
599 lsig[1] = '\0';
600 }
601 }
602
wildcmp(const char * wild,const char * string)603 int wildcmp(const char *wild, const char *string)
604 { //case-insensitive string compare with wildcards. returns true for a match.
605 while (*string)
606 {
607 if (*wild == '*')
608 {
609 if (*string == '/' || *string == '\\')
610 {
611 //* terminates if we get a match on the char following it, or if its a \ or / char
612 wild++;
613 continue;
614 }
615 if (wildcmp(wild+1, string))
616 return true;
617 string++;
618 }
619 else if ((q_tolower(*wild) == q_tolower(*string)) || (*wild == '?'))
620 {
621 //this char matches
622 wild++;
623 string++;
624 }
625 else
626 {
627 //failure
628 return false;
629 }
630 }
631
632 while (*wild == '*')
633 {
634 wild++;
635 }
636 return !*wild;
637 }
638
Info_RemoveKey(char * info,const char * key)639 void Info_RemoveKey(char *info, const char *key)
640 { //only shrinks, so no need for max size.
641 size_t keylen = strlen(key);
642
643 while(*info)
644 {
645 char *l = info;
646 if (*info++ != '\\')
647 break; //error / end-of-string
648
649 if (!strncmp(info, key, keylen) && info[keylen] == '\\')
650 {
651 //skip the key name
652 info += keylen+1;
653 //this is the old value for the key. skip over it
654 while (*info && *info != '\\')
655 info++;
656
657 //okay, we found it. strip it out now.
658 memmove(l, info, strlen(info)+1);
659 return;
660 }
661 else
662 {
663 //skip the key
664 while (*info && *info != '\\')
665 info++;
666
667 //validate that its a value now
668 if (*info++ != '\\')
669 break; //error
670 //skip the value
671 while (*info && *info != '\\')
672 info++;
673 }
674 }
675 }
Info_SetKey(char * info,size_t infosize,const char * key,const char * val)676 void Info_SetKey(char *info, size_t infosize, const char *key, const char *val)
677 {
678 size_t keylen = strlen(key);
679 size_t vallen = strlen(val);
680
681 Info_RemoveKey(info, key);
682
683 if (vallen)
684 {
685 char *o = info + strlen(info);
686 char *e = info + infosize-1;
687
688 if (!*key || strchr(key, '\\') || strchr(val, '\\'))
689 Con_Warning("Info_SetKey(%s): invalid key/value\n", key);
690 else if (o + 2 + keylen + vallen >= e)
691 Con_Warning("Info_SetKey(%s): length exceeds max\n", key);
692 else
693 {
694 *o++ = '\\';
695 memcpy(o, key, keylen);
696 o += keylen;
697 *o++ = '\\';
698 memcpy(o, val, vallen);
699 o += vallen;
700
701 *o = 0;
702 }
703 }
704 }
Info_GetKey(const char * info,const char * key,char * out,size_t outsize)705 const char *Info_GetKey(const char *info, const char *key, char *out, size_t outsize)
706 {
707 const char *r = out;
708 size_t keylen = strlen(key);
709
710 outsize--;
711
712 while(*info)
713 {
714 if (*info++ != '\\')
715 break; //error / end-of-string
716
717 if (!strncmp(info, key, keylen) && info[keylen] == '\\')
718 {
719 //skip the key name
720 info += keylen+1;
721 //this is the value for the key. copy it out
722 while (*info && *info != '\\' && outsize-->0)
723 *out++ = *info++;
724 break;
725 }
726 else
727 {
728 //skip the key
729 while (*info && *info != '\\')
730 info++;
731
732 //validate that its a value now
733 if (*info++ != '\\')
734 break; //error
735 //skip the value
736 while (*info && *info != '\\')
737 info++;
738 }
739 }
740 *out = 0;
741 return r;
742 }
743
Info_Enumerate(const char * info,void (* cb)(void * ctx,const char * key,const char * value),void * cbctx)744 void Info_Enumerate(const char *info, void(*cb)(void *ctx, const char *key, const char *value), void *cbctx)
745 {
746 char key[1024];
747 char val[1024];
748 size_t kl, vl;
749 while(*info)
750 {
751 kl=vl=0;
752 if (*info++ != '\\')
753 break; //error / end-of-string
754
755 //skip the key
756 while (*info && *info != '\\')
757 {
758 if (kl < sizeof(key)-1)
759 key[kl++] = *info;
760 info++;
761 }
762
763 //validate that its a value now
764 if (*info++ != '\\')
765 break; //error
766 //skip the value
767 while (*info && *info != '\\')
768 {
769 if (vl < sizeof(val)-1)
770 val[vl++] = *info;
771 info++;
772 }
773
774 key[kl] = 0;
775 val[vl] = 0;
776 cb(cbctx, key, val);
777 }
778 }
Info_Print_Callback(void * ctx,const char * key,const char * val)779 static void Info_Print_Callback(void *ctx, const char *key, const char *val)
780 {
781 Con_Printf("%20s: %s\n", key, val);
782 }
Info_Print(const char * info)783 void Info_Print(const char *info)
784 {
785 Info_Enumerate(info, Info_Print_Callback, NULL);
786 }
787 /*
788 ============================================================================
789
790 BYTE ORDER FUNCTIONS
791
792 ============================================================================
793 */
794
795 qboolean host_bigendian;
796
797 short (*BigShort) (short l);
798 short (*LittleShort) (short l);
799 int (*BigLong) (int l);
800 int (*LittleLong) (int l);
801 float (*BigFloat) (float l);
802 float (*LittleFloat) (float l);
803
ShortSwap(short l)804 short ShortSwap (short l)
805 {
806 byte b1, b2;
807
808 b1 = l&255;
809 b2 = (l>>8)&255;
810
811 return ((unsigned short)b1<<8) + b2;
812 }
813
ShortNoSwap(short l)814 short ShortNoSwap (short l)
815 {
816 return l;
817 }
818
LongSwap(int l)819 int LongSwap (int l)
820 {
821 byte b1, b2, b3, b4;
822
823 b1 = l&255;
824 b2 = (l>>8)&255;
825 b3 = (l>>16)&255;
826 b4 = (l>>24)&255;
827
828 return ((unsigned int)b1<<24) + ((unsigned int)b2<<16) + ((unsigned int)b3<<8) + b4;
829 }
830
LongNoSwap(int l)831 int LongNoSwap (int l)
832 {
833 return l;
834 }
835
FloatSwap(float f)836 float FloatSwap (float f)
837 {
838 union
839 {
840 float f;
841 byte b[4];
842 } dat1, dat2;
843
844
845 dat1.f = f;
846 dat2.b[0] = dat1.b[3];
847 dat2.b[1] = dat1.b[2];
848 dat2.b[2] = dat1.b[1];
849 dat2.b[3] = dat1.b[0];
850 return dat2.f;
851 }
852
FloatNoSwap(float f)853 float FloatNoSwap (float f)
854 {
855 return f;
856 }
857
858 /*
859 ==============================================================================
860
861 MESSAGE IO FUNCTIONS
862
863 Handles byte ordering and avoids alignment errors
864 ==============================================================================
865 */
866
867 //
868 // writing functions
869 //
870
MSG_WriteChar(sizebuf_t * sb,int c)871 void MSG_WriteChar (sizebuf_t *sb, int c)
872 {
873 byte *buf;
874
875 #ifdef PARANOID
876 if (c < -128 || c > 127)
877 Sys_Error ("MSG_WriteChar: range error");
878 #endif
879
880 buf = (byte *) SZ_GetSpace (sb, 1);
881 buf[0] = c;
882 }
883
MSG_WriteByte(sizebuf_t * sb,int c)884 void MSG_WriteByte (sizebuf_t *sb, int c)
885 {
886 byte *buf;
887
888 #ifdef PARANOID
889 if (c < 0 || c > 255)
890 Sys_Error ("MSG_WriteByte: range error");
891 #endif
892
893 buf = (byte *) SZ_GetSpace (sb, 1);
894 buf[0] = c;
895 }
896
MSG_WriteShort(sizebuf_t * sb,int c)897 void MSG_WriteShort (sizebuf_t *sb, int c)
898 {
899 byte *buf;
900
901 #ifdef PARANOID
902 if (c < ((short)0x8000) || c > (short)0x7fff)
903 Sys_Error ("MSG_WriteShort: range error");
904 #endif
905
906 buf = (byte *) SZ_GetSpace (sb, 2);
907 buf[0] = c&0xff;
908 buf[1] = c>>8;
909 }
910
MSG_WriteLong(sizebuf_t * sb,int c)911 void MSG_WriteLong (sizebuf_t *sb, int c)
912 {
913 byte *buf;
914
915 buf = (byte *) SZ_GetSpace (sb, 4);
916 buf[0] = c&0xff;
917 buf[1] = (c>>8)&0xff;
918 buf[2] = (c>>16)&0xff;
919 buf[3] = c>>24;
920 }
921
MSG_WriteFloat(sizebuf_t * sb,float f)922 void MSG_WriteFloat (sizebuf_t *sb, float f)
923 {
924 union
925 {
926 float f;
927 int l;
928 } dat;
929
930 dat.f = f;
931 dat.l = LittleLong (dat.l);
932
933 SZ_Write (sb, &dat.l, 4);
934 }
935
MSG_WriteString(sizebuf_t * sb,const char * s)936 void MSG_WriteString (sizebuf_t *sb, const char *s)
937 {
938 if (!s)
939 SZ_Write (sb, "", 1);
940 else
941 SZ_Write (sb, s, Q_strlen(s)+1);
942 }
MSG_WriteStringUnterminated(sizebuf_t * sb,const char * s)943 void MSG_WriteStringUnterminated (sizebuf_t *sb, const char *s)
944 {
945 SZ_Write (sb, s, Q_strlen(s));
946 }
947
948 //johnfitz -- original behavior, 13.3 fixed point coords, max range +-4096
MSG_WriteCoord16(sizebuf_t * sb,float f)949 void MSG_WriteCoord16 (sizebuf_t *sb, float f)
950 {
951 MSG_WriteShort (sb, Q_rint(f*8));
952 }
953
954 //johnfitz -- 16.8 fixed point coords, max range +-32768
MSG_WriteCoord24(sizebuf_t * sb,float f)955 void MSG_WriteCoord24 (sizebuf_t *sb, float f)
956 {
957 MSG_WriteShort (sb, f);
958 MSG_WriteByte (sb, (int)(f*255)%255);
959 }
960
961 //johnfitz -- 32-bit float coords
MSG_WriteCoord32f(sizebuf_t * sb,float f)962 void MSG_WriteCoord32f (sizebuf_t *sb, float f)
963 {
964 MSG_WriteFloat (sb, f);
965 }
966
MSG_WriteCoord(sizebuf_t * sb,float f,unsigned int flags)967 void MSG_WriteCoord (sizebuf_t *sb, float f, unsigned int flags)
968 {
969 if (flags & PRFL_FLOATCOORD)
970 MSG_WriteFloat (sb, f);
971 else if (flags & PRFL_INT32COORD)
972 MSG_WriteLong (sb, Q_rint (f * 16));
973 else if (flags & PRFL_24BITCOORD)
974 MSG_WriteCoord24 (sb, f);
975 else MSG_WriteCoord16 (sb, f);
976 }
977
MSG_WriteAngle(sizebuf_t * sb,float f,unsigned int flags)978 void MSG_WriteAngle (sizebuf_t *sb, float f, unsigned int flags)
979 {
980 if (flags & PRFL_FLOATANGLE)
981 MSG_WriteFloat (sb, f);
982 else if (flags & PRFL_SHORTANGLE)
983 MSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535);
984 else MSG_WriteByte (sb, Q_rint(f * 256.0 / 360.0) & 255); //johnfitz -- use Q_rint instead of (int) }
985 }
986
987 //johnfitz -- for PROTOCOL_FITZQUAKE
MSG_WriteAngle16(sizebuf_t * sb,float f,unsigned int flags)988 void MSG_WriteAngle16 (sizebuf_t *sb, float f, unsigned int flags)
989 {
990 if (flags & PRFL_FLOATANGLE)
991 MSG_WriteFloat (sb, f);
992 else MSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535);
993 }
994 //johnfitz
995
996 //spike -- for PEXT2_REPLACEMENTDELTAS
MSG_WriteEntity(sizebuf_t * sb,unsigned int entnum,unsigned int pext2)997 void MSG_WriteEntity (sizebuf_t *sb, unsigned int entnum, unsigned int pext2)
998 {
999 //high short, low byte
1000 if (entnum > 0x7fff && (pext2 & PEXT2_REPLACEMENTDELTAS))
1001 {
1002 MSG_WriteShort(sb, 0x8000|(entnum>>8));
1003 MSG_WriteByte(sb, entnum&0xff);
1004 }
1005 else
1006 MSG_WriteShort(sb, entnum);
1007 }
1008
1009 //
1010 // reading functions
1011 //
1012 int msg_readcount;
1013 qboolean msg_badread;
1014
MSG_BeginReading(void)1015 void MSG_BeginReading (void)
1016 {
1017 msg_readcount = 0;
1018 msg_badread = false;
1019 }
1020
1021 // returns -1 and sets msg_badread if no more characters are available
MSG_ReadChar(void)1022 int MSG_ReadChar (void)
1023 {
1024 int c;
1025
1026 if (msg_readcount+1 > net_message.cursize)
1027 {
1028 msg_badread = true;
1029 return -1;
1030 }
1031
1032 c = (signed char)net_message.data[msg_readcount];
1033 msg_readcount++;
1034
1035 return c;
1036 }
1037
MSG_ReadByte(void)1038 int MSG_ReadByte (void)
1039 {
1040 int c;
1041
1042 if (msg_readcount+1 > net_message.cursize)
1043 {
1044 msg_badread = true;
1045 return -1;
1046 }
1047
1048 c = (unsigned char)net_message.data[msg_readcount];
1049 msg_readcount++;
1050
1051 return c;
1052 }
1053
MSG_ReadShort(void)1054 int MSG_ReadShort (void)
1055 {
1056 int c;
1057
1058 if (msg_readcount+2 > net_message.cursize)
1059 {
1060 msg_badread = true;
1061 return -1;
1062 }
1063
1064 c = (short)(net_message.data[msg_readcount]
1065 + (net_message.data[msg_readcount+1]<<8));
1066
1067 msg_readcount += 2;
1068
1069 return c;
1070 }
1071
MSG_ReadLong(void)1072 int MSG_ReadLong (void)
1073 {
1074 uint32_t c;
1075
1076 if (msg_readcount+4 > net_message.cursize)
1077 {
1078 msg_badread = true;
1079 return -1;
1080 }
1081
1082 c = (uint32_t)net_message.data[msg_readcount]
1083 + ((uint32_t)(net_message.data[msg_readcount+1])<<8)
1084 + ((uint32_t)(net_message.data[msg_readcount+2])<<16)
1085 + ((uint32_t)(net_message.data[msg_readcount+3])<<24);
1086
1087 msg_readcount += 4;
1088
1089 return c;
1090 }
1091
MSG_ReadFloat(void)1092 float MSG_ReadFloat (void)
1093 {
1094 union
1095 {
1096 byte b[4];
1097 float f;
1098 int l;
1099 } dat;
1100
1101 dat.b[0] = net_message.data[msg_readcount];
1102 dat.b[1] = net_message.data[msg_readcount+1];
1103 dat.b[2] = net_message.data[msg_readcount+2];
1104 dat.b[3] = net_message.data[msg_readcount+3];
1105 msg_readcount += 4;
1106
1107 dat.l = LittleLong (dat.l);
1108
1109 return dat.f;
1110 }
1111
MSG_ReadString(void)1112 const char *MSG_ReadString (void)
1113 {
1114 static char string[2048];
1115 int c;
1116 size_t l;
1117
1118 l = 0;
1119 do
1120 {
1121 c = MSG_ReadByte ();
1122 if (c == -1 || c == 0)
1123 break;
1124 string[l] = c;
1125 l++;
1126 } while (l < sizeof(string) - 1);
1127
1128 string[l] = 0;
1129
1130 return string;
1131 }
1132
1133 //johnfitz -- original behavior, 13.3 fixed point coords, max range +-4096
MSG_ReadCoord16(void)1134 float MSG_ReadCoord16 (void)
1135 {
1136 return MSG_ReadShort() * (1.0/8);
1137 }
1138
1139 //johnfitz -- 16.8 fixed point coords, max range +-32768
MSG_ReadCoord24(void)1140 float MSG_ReadCoord24 (void)
1141 {
1142 return MSG_ReadShort() + MSG_ReadByte() * (1.0/255);
1143 }
1144
1145 //johnfitz -- 32-bit float coords
MSG_ReadCoord32f(void)1146 float MSG_ReadCoord32f (void)
1147 {
1148 return MSG_ReadFloat();
1149 }
1150
MSG_ReadCoord(unsigned int flags)1151 float MSG_ReadCoord (unsigned int flags)
1152 {
1153 if (flags & PRFL_FLOATCOORD)
1154 return MSG_ReadFloat ();
1155 else if (flags & PRFL_INT32COORD)
1156 return MSG_ReadLong () * (1.0 / 16.0);
1157 else if (flags & PRFL_24BITCOORD)
1158 return MSG_ReadCoord24 ();
1159 else return MSG_ReadCoord16 ();
1160 }
1161
MSG_ReadAngle(unsigned int flags)1162 float MSG_ReadAngle (unsigned int flags)
1163 {
1164 if (flags & PRFL_FLOATANGLE)
1165 return MSG_ReadFloat ();
1166 else if (flags & PRFL_SHORTANGLE)
1167 return MSG_ReadShort () * (360.0 / 65536);
1168 else return MSG_ReadChar () * (360.0 / 256);
1169 }
1170
1171 //johnfitz -- for PROTOCOL_FITZQUAKE
MSG_ReadAngle16(unsigned int flags)1172 float MSG_ReadAngle16 (unsigned int flags)
1173 {
1174 if (flags & PRFL_FLOATANGLE)
1175 return MSG_ReadFloat (); // make sure
1176 else return MSG_ReadShort () * (360.0 / 65536);
1177 }
1178 //johnfitz
1179
MSG_ReadEntity(unsigned int pext2)1180 unsigned int MSG_ReadEntity(unsigned int pext2)
1181 {
1182 unsigned int e = (unsigned short)MSG_ReadShort();
1183 if (pext2 & PEXT2_REPLACEMENTDELTAS)
1184 {
1185 if (e & 0x8000)
1186 {
1187 e = (e & 0x7fff) << 8;
1188 e |= MSG_ReadByte();
1189 }
1190 }
1191 return e;
1192 }
1193
1194 //===========================================================================
1195
SZ_Alloc(sizebuf_t * buf,int startsize)1196 void SZ_Alloc (sizebuf_t *buf, int startsize)
1197 {
1198 if (startsize < 256)
1199 startsize = 256;
1200 buf->data = (byte *) Hunk_AllocName (startsize, "sizebuf");
1201 buf->maxsize = startsize;
1202 buf->cursize = 0;
1203 }
1204
1205
SZ_Free(sizebuf_t * buf)1206 void SZ_Free (sizebuf_t *buf)
1207 {
1208 // Z_Free (buf->data);
1209 // buf->data = NULL;
1210 // buf->maxsize = 0;
1211 buf->cursize = 0;
1212 }
1213
SZ_Clear(sizebuf_t * buf)1214 void SZ_Clear (sizebuf_t *buf)
1215 {
1216 buf->cursize = 0;
1217 buf->overflowed = false;
1218 }
1219
SZ_GetSpace(sizebuf_t * buf,int length)1220 void *SZ_GetSpace (sizebuf_t *buf, int length)
1221 {
1222 void *data;
1223
1224 if (buf->cursize + length > buf->maxsize)
1225 {
1226 if (!buf->allowoverflow)
1227 Host_Error ("SZ_GetSpace: overflow without allowoverflow set"); // ericw -- made Host_Error to be less annoying
1228
1229 if (length > buf->maxsize)
1230 Sys_Error ("SZ_GetSpace: %i is > full buffer size", length);
1231
1232 Con_Printf ("SZ_GetSpace: overflow\n");
1233 SZ_Clear (buf);
1234 buf->overflowed = true;
1235 }
1236
1237 data = buf->data + buf->cursize;
1238 buf->cursize += length;
1239
1240 return data;
1241 }
1242
SZ_Write(sizebuf_t * buf,const void * data,int length)1243 void SZ_Write (sizebuf_t *buf, const void *data, int length)
1244 {
1245 Q_memcpy (SZ_GetSpace(buf,length),data,length);
1246 }
1247
SZ_Print(sizebuf_t * buf,const char * data)1248 void SZ_Print (sizebuf_t *buf, const char *data)
1249 {
1250 int len = Q_strlen(data) + 1;
1251
1252 if (buf->data[buf->cursize-1])
1253 { /* no trailing 0 */
1254 Q_memcpy ((byte *)SZ_GetSpace(buf, len ) , data, len);
1255 }
1256 else
1257 { /* write over trailing 0 */
1258 Q_memcpy ((byte *)SZ_GetSpace(buf, len-1)-1, data, len);
1259 }
1260 }
1261
1262
1263 //============================================================================
1264
1265 /*
1266 ============
1267 COM_SkipPath
1268 ============
1269 */
COM_SkipPath(const char * pathname)1270 const char *COM_SkipPath (const char *pathname)
1271 {
1272 const char *last;
1273
1274 last = pathname;
1275 while (*pathname)
1276 {
1277 if (*pathname == '/')
1278 last = pathname + 1;
1279 pathname++;
1280 }
1281 return last;
1282 }
1283
1284 /*
1285 ============
1286 COM_StripExtension
1287 ============
1288 */
COM_StripExtension(const char * in,char * out,size_t outsize)1289 void COM_StripExtension (const char *in, char *out, size_t outsize)
1290 {
1291 int length;
1292
1293 if (!*in)
1294 {
1295 *out = '\0';
1296 return;
1297 }
1298 if (in != out) /* copy when not in-place editing */
1299 q_strlcpy (out, in, outsize);
1300 length = (int)strlen(out) - 1;
1301 while (length > 0 && out[length] != '.')
1302 {
1303 --length;
1304 if (out[length] == '/' || out[length] == '\\')
1305 return; /* no extension */
1306 }
1307 if (length > 0)
1308 out[length] = '\0';
1309 }
1310
1311 /*
1312 ============
1313 COM_FileGetExtension - doesn't return NULL
1314 ============
1315 */
COM_FileGetExtension(const char * in)1316 const char *COM_FileGetExtension (const char *in)
1317 {
1318 const char *src;
1319 size_t len;
1320
1321 len = strlen(in);
1322 if (len < 2) /* nothing meaningful */
1323 return "";
1324
1325 src = in + len - 1;
1326 while (src != in && src[-1] != '.')
1327 src--;
1328 if (src == in || strchr(src, '/') != NULL || strchr(src, '\\') != NULL)
1329 return ""; /* no extension, or parent directory has a dot */
1330
1331 return src;
1332 }
1333
1334 /*
1335 ============
1336 COM_ExtractExtension
1337 ============
1338 */
COM_ExtractExtension(const char * in,char * out,size_t outsize)1339 void COM_ExtractExtension (const char *in, char *out, size_t outsize)
1340 {
1341 const char *ext = COM_FileGetExtension (in);
1342 if (! *ext)
1343 *out = '\0';
1344 else
1345 q_strlcpy (out, ext, outsize);
1346 }
1347
1348 /*
1349 ============
1350 COM_FileBase
1351 take 'somedir/otherdir/filename.ext',
1352 write only 'filename' to the output
1353 ============
1354 */
COM_FileBase(const char * in,char * out,size_t outsize)1355 void COM_FileBase (const char *in, char *out, size_t outsize)
1356 {
1357 const char *dot, *slash, *s;
1358
1359 s = in;
1360 slash = in;
1361 dot = NULL;
1362 while (*s)
1363 {
1364 if (*s == '/')
1365 slash = s + 1;
1366 if (*s == '.')
1367 dot = s;
1368 s++;
1369 }
1370 if (dot == NULL)
1371 dot = s;
1372
1373 if (dot - slash < 2)
1374 q_strlcpy (out, "?model?", outsize);
1375 else
1376 {
1377 size_t len = dot - slash;
1378 if (len >= outsize)
1379 len = outsize - 1;
1380 memcpy (out, slash, len);
1381 out[len] = '\0';
1382 }
1383 }
1384
1385 /*
1386 ==================
1387 COM_DefaultExtension
1388 if path doesn't have a .EXT, append extension
1389 (extension should include the leading ".")
1390 ==================
1391 */
1392 #if 0 /* can be dangerous */
1393 void COM_DefaultExtension (char *path, const char *extension, size_t len)
1394 {
1395 char *src;
1396
1397 if (!*path) return;
1398 src = path + strlen(path) - 1;
1399
1400 while (*src != '/' && *src != '\\' && src != path)
1401 {
1402 if (*src == '.')
1403 return; // it has an extension
1404 src--;
1405 }
1406
1407 q_strlcat(path, extension, len);
1408 }
1409 #endif
1410
1411 /*
1412 ==================
1413 COM_AddExtension
1414 if path extension doesn't match .EXT, append it
1415 (extension should include the leading ".")
1416 ==================
1417 */
COM_AddExtension(char * path,const char * extension,size_t len)1418 void COM_AddExtension (char *path, const char *extension, size_t len)
1419 {
1420 if (strcmp(COM_FileGetExtension(path), extension + 1) != 0)
1421 q_strlcat(path, extension, len);
1422 }
1423
1424
1425 /*
1426 ==============
1427 COM_Parse
1428
1429 Parse a token out of a string
1430 ==============
1431 */
COM_Parse(const char * data)1432 const char *COM_Parse (const char *data)
1433 {
1434 int c;
1435 int len;
1436
1437 len = 0;
1438 com_token[0] = 0;
1439
1440 if (!data)
1441 return NULL;
1442
1443 // skip whitespace
1444 skipwhite:
1445 while ((c = *data) <= ' ')
1446 {
1447 if (c == 0)
1448 return NULL; // end of file
1449 data++;
1450 }
1451
1452 // skip // comments
1453 if (c == '/' && data[1] == '/')
1454 {
1455 while (*data && *data != '\n')
1456 data++;
1457 goto skipwhite;
1458 }
1459
1460 // skip /*..*/ comments
1461 if (c == '/' && data[1] == '*')
1462 {
1463 data += 2;
1464 while (*data && !(*data == '*' && data[1] == '/'))
1465 data++;
1466 if (*data)
1467 data += 2;
1468 goto skipwhite;
1469 }
1470
1471 // handle quoted strings specially
1472 if (c == '\"')
1473 {
1474 data++;
1475 while (1)
1476 {
1477 if ((c = *data) != 0)
1478 ++data;
1479 if (c == '\"' || !c)
1480 {
1481 com_token[len] = 0;
1482 return data;
1483 }
1484 com_token[len] = c;
1485 len++;
1486 }
1487 }
1488
1489 // parse single characters
1490 if (c == '{' || c == '}'|| c == '('|| c == ')' || c == '\'' || c == ':')
1491 {
1492 com_token[len] = c;
1493 len++;
1494 com_token[len] = 0;
1495 return data+1;
1496 }
1497
1498 // parse a regular word
1499 do
1500 {
1501 com_token[len] = c;
1502 data++;
1503 len++;
1504 c = *data;
1505 /* commented out the check for ':' so that ip:port works */
1506 if (c == '{' || c == '}'|| c == '('|| c == ')' || c == '\''/* || c == ':' */)
1507 break;
1508 } while (c > 32);
1509
1510 com_token[len] = 0;
1511 return data;
1512 }
1513
1514
1515 /*
1516 ================
1517 COM_CheckParm
1518
1519 Returns the position (1 to argc-1) in the program's argument list
1520 where the given parameter apears, or 0 if not present
1521 ================
1522 */
COM_CheckParmNext(int last,const char * parm)1523 int COM_CheckParmNext (int last, const char *parm)
1524 {
1525 int i;
1526
1527 for (i = last+1; i < com_argc; i++)
1528 {
1529 if (!com_argv[i])
1530 continue; // NEXTSTEP sometimes clears appkit vars.
1531 if (!Q_strcmp (parm,com_argv[i]))
1532 return i;
1533 }
1534
1535 return 0;
1536 }
COM_CheckParm(const char * parm)1537 int COM_CheckParm (const char *parm)
1538 {
1539 return COM_CheckParmNext(0, parm);
1540 }
1541
1542 /*
1543 ================
1544 COM_CheckRegistered
1545
1546 Looks for the pop.txt file and verifies it.
1547 Sets the "registered" cvar.
1548 Immediately exits out if an alternate game was attempted to be started without
1549 being registered.
1550 ================
1551 */
COM_CheckRegistered(void)1552 static void COM_CheckRegistered (void)
1553 {
1554 int h;
1555 unsigned short check[128];
1556 int i;
1557
1558 COM_OpenFile("gfx/pop.lmp", &h, NULL);
1559
1560 if (h == -1)
1561 {
1562 Cvar_SetROM ("registered", "0");
1563 Con_Printf ("Playing shareware version.\n");
1564 if (com_modified)
1565 Sys_Error ("You must have the registered version to use modified games.\n\n"
1566 "Basedir is: %s\n\n"
1567 "Check that this has an " GAMENAME " subdirectory containing pak0.pak and pak1.pak, "
1568 "or use the -basedir command-line option to specify another directory.",
1569 com_basedir);
1570 return;
1571 }
1572
1573 Sys_FileRead (h, check, sizeof(check));
1574 COM_CloseFile (h);
1575
1576 for (i = 0; i < 128; i++)
1577 {
1578 if (pop[i] != (unsigned short)BigShort (check[i]))
1579 Sys_Error ("Corrupted data file.");
1580 }
1581
1582 for (i = 0; com_cmdline[i]; i++)
1583 {
1584 if (com_cmdline[i]!= ' ')
1585 break;
1586 }
1587
1588 Cvar_SetROM ("cmdline", &com_cmdline[i]);
1589 Cvar_SetROM ("registered", "1");
1590 Con_Printf ("Playing registered version.\n");
1591 }
1592
1593
1594 /*
1595 ================
1596 COM_InitArgv
1597 ================
1598 */
COM_InitArgv(int argc,char ** argv)1599 void COM_InitArgv (int argc, char **argv)
1600 {
1601 int i, j, n;
1602
1603 // reconstitute the command line for the cmdline externally visible cvar
1604 n = 0;
1605
1606 for (j = 0; (j<MAX_NUM_ARGVS) && (j< argc); j++)
1607 {
1608 i = 0;
1609
1610 while ((n < (CMDLINE_LENGTH - 1)) && argv[j][i])
1611 {
1612 com_cmdline[n++] = argv[j][i++];
1613 }
1614
1615 if (n < (CMDLINE_LENGTH - 1))
1616 com_cmdline[n++] = ' ';
1617 else
1618 break;
1619 }
1620
1621 if (n > 0 && com_cmdline[n-1] == ' ')
1622 com_cmdline[n-1] = 0; //johnfitz -- kill the trailing space
1623
1624 Con_Printf("Command line: %s\n", com_cmdline);
1625
1626 for (com_argc = 0; (com_argc < MAX_NUM_ARGVS) && (com_argc < argc); com_argc++)
1627 {
1628 largv[com_argc] = argv[com_argc];
1629 if (!Q_strcmp ("-safe", argv[com_argc]))
1630 safemode = 1;
1631 }
1632
1633 largv[com_argc] = argvdummy;
1634 com_argv = largv;
1635
1636 if (COM_CheckParm ("-rogue"))
1637 {
1638 rogue = true;
1639 standard_quake = false;
1640 }
1641
1642 if (COM_CheckParm ("-hipnotic") || COM_CheckParm ("-quoth")) //johnfitz -- "-quoth" support
1643 {
1644 hipnotic = true;
1645 standard_quake = false;
1646 }
1647 }
1648
1649
1650 entity_state_t nullentitystate;
COM_SetupNullState(void)1651 static void COM_SetupNullState(void)
1652 {
1653 //the null state has some specific default values
1654 // nullentitystate.drawflags = /*SCALE_ORIGIN_ORIGIN*/96;
1655 nullentitystate.colormod[0] = 32;
1656 nullentitystate.colormod[1] = 32;
1657 nullentitystate.colormod[2] = 32;
1658 // nullentitystate.glowmod[0] = 32;
1659 // nullentitystate.glowmod[1] = 32;
1660 // nullentitystate.glowmod[2] = 32;
1661 nullentitystate.alpha = 0; //fte has 255 by default, with 0 for invisible. fitz uses 1 for invisible, 0 default, and 255=full alpha
1662 nullentitystate.scale = 16;
1663 // nullentitystate.solidsize = 0;//ES_SOLID_BSP;
1664 }
1665
1666 /*
1667 ================
1668 COM_Init
1669 ================
1670 */
COM_Init(void)1671 void COM_Init (void)
1672 {
1673 int i = 0x12345678;
1674 /* U N I X */
1675
1676 /*
1677 BE_ORDER: 12 34 56 78
1678 U N I X
1679
1680 LE_ORDER: 78 56 34 12
1681 X I N U
1682
1683 PDP_ORDER: 34 12 78 56
1684 N U X I
1685 */
1686 if ( *(char *)&i == 0x12 )
1687 host_bigendian = true;
1688 else if ( *(char *)&i == 0x78 )
1689 host_bigendian = false;
1690 else /* if ( *(char *)&i == 0x34 ) */
1691 Sys_Error ("Unsupported endianism.");
1692
1693 if (host_bigendian)
1694 {
1695 BigShort = ShortNoSwap;
1696 LittleShort = ShortSwap;
1697 BigLong = LongNoSwap;
1698 LittleLong = LongSwap;
1699 BigFloat = FloatNoSwap;
1700 LittleFloat = FloatSwap;
1701 }
1702 else /* assumed LITTLE_ENDIAN. */
1703 {
1704 BigShort = ShortSwap;
1705 LittleShort = ShortNoSwap;
1706 BigLong = LongSwap;
1707 LittleLong = LongNoSwap;
1708 BigFloat = FloatSwap;
1709 LittleFloat = FloatNoSwap;
1710 }
1711
1712 if (COM_CheckParm("-fitz"))
1713 fitzmode = true;
1714
1715 COM_SetupNullState();
1716 }
1717
1718
1719 /*
1720 ============
1721 va
1722
1723 does a varargs printf into a temp buffer. cycles between
1724 4 different static buffers. the number of buffers cycled
1725 is defined in VA_NUM_BUFFS.
1726 FIXME: make this buffer size safe someday
1727 ============
1728 */
1729 #define VA_NUM_BUFFS 4
1730 #define VA_BUFFERLEN 1024
1731
get_va_buffer(void)1732 static char *get_va_buffer(void)
1733 {
1734 static char va_buffers[VA_NUM_BUFFS][VA_BUFFERLEN];
1735 static int buffer_idx = 0;
1736 buffer_idx = (buffer_idx + 1) & (VA_NUM_BUFFS - 1);
1737 return va_buffers[buffer_idx];
1738 }
1739
va(const char * format,...)1740 char *va (const char *format, ...)
1741 {
1742 va_list argptr;
1743 char *va_buf;
1744
1745 va_buf = get_va_buffer ();
1746 va_start (argptr, format);
1747 q_vsnprintf (va_buf, VA_BUFFERLEN, format, argptr);
1748 va_end (argptr);
1749
1750 return va_buf;
1751 }
1752
1753 /*
1754 =============================================================================
1755
1756 QUAKE FILESYSTEM
1757
1758 =============================================================================
1759 */
1760
1761 int com_filesize;
1762
1763
1764 //
1765 // on-disk pakfile
1766 //
1767 typedef struct
1768 {
1769 char name[56];
1770 int filepos, filelen;
1771 } dpackfile_t;
1772
1773 typedef struct
1774 {
1775 char id[4];
1776 int dirofs;
1777 int dirlen;
1778 } dpackheader_t;
1779
1780 #define MAX_FILES_IN_PACK 2048
1781
1782 char com_gamenames[1024]; //eg: "hipnotic;quoth;warp" ... no id1
1783 char com_gamedir[MAX_OSPATH];
1784 char com_basedir[MAX_OSPATH];
1785 int file_from_pak; // ZOID: global indicating that file came from a pak
1786
1787 searchpath_t *com_searchpaths;
1788 searchpath_t *com_base_searchpaths;
1789
1790 /*
1791 ============
1792 COM_Path_f
1793 ============
1794 */
COM_Path_f(void)1795 static void COM_Path_f (void)
1796 {
1797 searchpath_t *s;
1798
1799 Con_Printf ("Current search path:\n");
1800 for (s = com_searchpaths; s; s = s->next)
1801 {
1802 if (s->pack)
1803 {
1804 Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
1805 }
1806 else
1807 Con_Printf ("%s\n", s->filename);
1808 }
1809 }
1810
1811 /*
1812 ============
1813 COM_WriteFile
1814
1815 The filename will be prefixed by the current game directory
1816 ============
1817 */
COM_WriteFile(const char * filename,const void * data,int len)1818 void COM_WriteFile (const char *filename, const void *data, int len)
1819 {
1820 int handle;
1821 char name[MAX_OSPATH];
1822
1823 Sys_mkdir (com_gamedir); //johnfitz -- if we've switched to a nonexistant gamedir, create it now so we don't crash
1824
1825 q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, filename);
1826
1827 handle = Sys_FileOpenWrite (name);
1828 if (handle == -1)
1829 {
1830 Sys_Printf ("COM_WriteFile: failed on %s\n", name);
1831 return;
1832 }
1833
1834 Sys_Printf ("COM_WriteFile: %s\n", name);
1835 Sys_FileWrite (handle, data, len);
1836 Sys_FileClose (handle);
1837 }
1838
1839 /*
1840 ============
1841 COM_CreatePath
1842 ============
1843 */
COM_CreatePath(char * path)1844 void COM_CreatePath (char *path)
1845 {
1846 char *ofs;
1847
1848 for (ofs = path + 1; *ofs; ofs++)
1849 {
1850 if (*ofs == '/')
1851 { // create the directory
1852 *ofs = 0;
1853 Sys_mkdir (path);
1854 *ofs = '/';
1855 }
1856 }
1857 }
1858
1859 /*
1860 ================
1861 COM_filelength
1862 ================
1863 */
COM_filelength(FILE * f)1864 long COM_filelength (FILE *f)
1865 {
1866 long pos, end;
1867
1868 pos = ftell (f);
1869 fseek (f, 0, SEEK_END);
1870 end = ftell (f);
1871 fseek (f, pos, SEEK_SET);
1872
1873 return end;
1874 }
1875
1876 /*
1877 ===========
1878 COM_FindFile
1879
1880 Finds the file in the search path.
1881 Sets com_filesize and one of handle or file
1882 If neither of file or handle is set, this
1883 can be used for detecting a file's presence.
1884 ===========
1885 */
COM_FindFile(const char * filename,int * handle,FILE ** file,unsigned int * path_id)1886 static int COM_FindFile (const char *filename, int *handle, FILE **file,
1887 unsigned int *path_id)
1888 {
1889 searchpath_t *search;
1890 char netpath[MAX_OSPATH];
1891 pack_t *pak;
1892 int i, findtime;
1893
1894 if (file && handle)
1895 Sys_Error ("COM_FindFile: both handle and file set");
1896
1897 file_from_pak = 0;
1898
1899 //
1900 // search through the path, one element at a time
1901 //
1902 for (search = com_searchpaths; search; search = search->next)
1903 {
1904 if (search->pack) /* look through all the pak file elements */
1905 {
1906 pak = search->pack;
1907 for (i = 0; i < pak->numfiles; i++)
1908 {
1909 if (strcmp(pak->files[i].name, filename) != 0)
1910 continue;
1911 // found it!
1912 com_filesize = pak->files[i].filelen;
1913 file_from_pak = 1;
1914 if (path_id)
1915 *path_id = search->path_id;
1916 if (handle)
1917 {
1918 *handle = pak->handle;
1919 Sys_FileSeek (pak->handle, pak->files[i].filepos);
1920 return com_filesize;
1921 }
1922 else if (file)
1923 { /* open a new file on the pakfile */
1924 *file = fopen (pak->filename, "rb");
1925 if (*file)
1926 fseek (*file, pak->files[i].filepos, SEEK_SET);
1927 return com_filesize;
1928 }
1929 else /* for COM_FileExists() */
1930 {
1931 return com_filesize;
1932 }
1933 }
1934 }
1935 else /* check a file in the directory tree */
1936 {
1937 if (!registered.value)
1938 { /* if not a registered version, don't ever go beyond base */
1939 if ( strchr (filename, '/') || strchr (filename,'\\'))
1940 continue;
1941 }
1942
1943 q_snprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename);
1944 findtime = Sys_FileTime (netpath);
1945 if (findtime == -1)
1946 continue;
1947
1948 if (path_id)
1949 *path_id = search->path_id;
1950 if (handle)
1951 {
1952 com_filesize = Sys_FileOpenRead (netpath, &i);
1953 *handle = i;
1954 return com_filesize;
1955 }
1956 else if (file)
1957 {
1958 *file = fopen (netpath, "rb");
1959 com_filesize = (*file == NULL) ? -1 : COM_filelength (*file);
1960 return com_filesize;
1961 }
1962 else
1963 {
1964 return 0; /* dummy valid value for COM_FileExists() */
1965 }
1966 }
1967 }
1968
1969 if (strcmp(COM_FileGetExtension(filename), "pcx") != 0
1970 && strcmp(COM_FileGetExtension(filename), "tga") != 0
1971 && strcmp(COM_FileGetExtension(filename), "lit") != 0
1972 && strcmp(COM_FileGetExtension(filename), "vis") != 0
1973 && strcmp(COM_FileGetExtension(filename), "ent") != 0)
1974 Con_DPrintf ("FindFile: can't find %s\n", filename);
1975 else Con_DPrintf2("FindFile: can't find %s\n", filename);
1976
1977 if (handle)
1978 *handle = -1;
1979 if (file)
1980 *file = NULL;
1981 com_filesize = -1;
1982 return com_filesize;
1983 }
1984
1985
1986 /*
1987 ===========
1988 COM_FileExists
1989
1990 Returns whether the file is found in the quake filesystem.
1991 ===========
1992 */
COM_FileExists(const char * filename,unsigned int * path_id)1993 qboolean COM_FileExists (const char *filename, unsigned int *path_id)
1994 {
1995 int ret = COM_FindFile (filename, NULL, NULL, path_id);
1996 return (ret == -1) ? false : true;
1997 }
1998
1999 /*
2000 ===========
2001 COM_OpenFile
2002
2003 filename never has a leading slash, but may contain directory walks
2004 returns a handle and a length
2005 it may actually be inside a pak file
2006 ===========
2007 */
COM_OpenFile(const char * filename,int * handle,unsigned int * path_id)2008 int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id)
2009 {
2010 return COM_FindFile (filename, handle, NULL, path_id);
2011 }
2012
2013 /*
2014 ===========
2015 COM_FOpenFile
2016
2017 If the requested file is inside a packfile, a new FILE * will be opened
2018 into the file.
2019 ===========
2020 */
COM_FOpenFile(const char * filename,FILE ** file,unsigned int * path_id)2021 int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id)
2022 {
2023 return COM_FindFile (filename, NULL, file, path_id);
2024 }
2025
2026 /*
2027 ============
2028 COM_CloseFile
2029
2030 If it is a pak file handle, don't really close it
2031 ============
2032 */
COM_CloseFile(int h)2033 void COM_CloseFile (int h)
2034 {
2035 searchpath_t *s;
2036
2037 for (s = com_searchpaths; s; s = s->next)
2038 if (s->pack && s->pack->handle == h)
2039 return;
2040
2041 Sys_FileClose (h);
2042 }
2043
2044
2045 /*
2046 ============
2047 COM_LoadFile
2048
2049 Filename are reletive to the quake directory.
2050 Allways appends a 0 byte.
2051 ============
2052 */
2053 #define LOADFILE_ZONE 0
2054 #define LOADFILE_HUNK 1
2055 #define LOADFILE_TEMPHUNK 2
2056 #define LOADFILE_CACHE 3
2057 #define LOADFILE_STACK 4
2058 #define LOADFILE_MALLOC 5
2059
2060 static byte *loadbuf;
2061 static cache_user_t *loadcache;
2062 static int loadsize;
2063
COM_LoadFile(const char * path,int usehunk,unsigned int * path_id)2064 byte *COM_LoadFile (const char *path, int usehunk, unsigned int *path_id)
2065 {
2066 int h;
2067 byte *buf;
2068 char base[32];
2069 int len;
2070
2071 buf = NULL; // quiet compiler warning
2072
2073 // look for it in the filesystem or pack files
2074 len = COM_OpenFile (path, &h, path_id);
2075 if (h == -1)
2076 return NULL;
2077
2078 // extract the filename base name for hunk tag
2079 COM_FileBase (path, base, sizeof(base));
2080
2081 switch (usehunk)
2082 {
2083 case LOADFILE_HUNK:
2084 buf = (byte *) Hunk_AllocName (len+1, base);
2085 break;
2086 case LOADFILE_TEMPHUNK:
2087 buf = (byte *) Hunk_TempAlloc (len+1);
2088 break;
2089 case LOADFILE_ZONE:
2090 buf = (byte *) Z_Malloc (len+1);
2091 break;
2092 case LOADFILE_CACHE:
2093 buf = (byte *) Cache_Alloc (loadcache, len+1, base);
2094 break;
2095 case LOADFILE_STACK:
2096 if (len < loadsize)
2097 buf = loadbuf;
2098 else
2099 buf = (byte *) Hunk_TempAlloc (len+1);
2100 break;
2101 case LOADFILE_MALLOC:
2102 buf = (byte *) malloc (len+1);
2103 break;
2104 default:
2105 Sys_Error ("COM_LoadFile: bad usehunk");
2106 }
2107
2108 if (!buf)
2109 Sys_Error ("COM_LoadFile: not enough space for %s", path);
2110
2111 ((byte *)buf)[len] = 0;
2112
2113 Sys_FileRead (h, buf, len);
2114 COM_CloseFile (h);
2115
2116 return buf;
2117 }
2118
COM_LoadHunkFile(const char * path,unsigned int * path_id)2119 byte *COM_LoadHunkFile (const char *path, unsigned int *path_id)
2120 {
2121 return COM_LoadFile (path, LOADFILE_HUNK, path_id);
2122 }
2123
COM_LoadZoneFile(const char * path,unsigned int * path_id)2124 byte *COM_LoadZoneFile (const char *path, unsigned int *path_id)
2125 {
2126 return COM_LoadFile (path, LOADFILE_ZONE, path_id);
2127 }
2128
COM_LoadTempFile(const char * path,unsigned int * path_id)2129 byte *COM_LoadTempFile (const char *path, unsigned int *path_id)
2130 {
2131 return COM_LoadFile (path, LOADFILE_TEMPHUNK, path_id);
2132 }
2133
COM_LoadCacheFile(const char * path,struct cache_user_s * cu,unsigned int * path_id)2134 void COM_LoadCacheFile (const char *path, struct cache_user_s *cu, unsigned int *path_id)
2135 {
2136 loadcache = cu;
2137 COM_LoadFile (path, LOADFILE_CACHE, path_id);
2138 }
2139
2140 // uses temp hunk if larger than bufsize
COM_LoadStackFile(const char * path,void * buffer,int bufsize,unsigned int * path_id)2141 byte *COM_LoadStackFile (const char *path, void *buffer, int bufsize, unsigned int *path_id)
2142 {
2143 byte *buf;
2144
2145 loadbuf = (byte *)buffer;
2146 loadsize = bufsize;
2147 buf = COM_LoadFile (path, LOADFILE_STACK, path_id);
2148
2149 return buf;
2150 }
2151
2152 // returns malloc'd memory
COM_LoadMallocFile(const char * path,unsigned int * path_id)2153 byte *COM_LoadMallocFile (const char *path, unsigned int *path_id)
2154 {
2155 return COM_LoadFile (path, LOADFILE_MALLOC, path_id);
2156 }
2157
COM_LoadMallocFile_TextMode_OSPath(const char * path,long * len_out)2158 byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out)
2159 {
2160 FILE *f;
2161 byte *data;
2162 long len, actuallen;
2163
2164 // ericw -- this is used by Host_Loadgame_f. Translate CRLF to LF on load games,
2165 // othewise multiline messages have a garbage character at the end of each line.
2166 // TODO: could handle in a way that allows loading CRLF savegames on mac/linux
2167 // without the junk characters appearing.
2168 f = fopen (path, "rt");
2169 if (f == NULL)
2170 return NULL;
2171
2172 len = COM_filelength (f);
2173 if (len < 0)
2174 return NULL;
2175
2176 data = (byte *) malloc (len + 1);
2177 if (data == NULL)
2178 return NULL;
2179
2180 // (actuallen < len) if CRLF to LF translation was performed
2181 actuallen = fread (data, 1, len, f);
2182 if (ferror(f))
2183 {
2184 free (data);
2185 return NULL;
2186 }
2187 data[actuallen] = '\0';
2188
2189 if (len_out != NULL)
2190 *len_out = actuallen;
2191 return data;
2192 }
2193
COM_ParseIntNewline(const char * buffer,int * value)2194 const char *COM_ParseIntNewline(const char *buffer, int *value)
2195 {
2196 int consumed = 0;
2197 sscanf (buffer, "%i\n%n", value, &consumed);
2198 return buffer + consumed;
2199 }
2200
COM_ParseFloatNewline(const char * buffer,float * value)2201 const char *COM_ParseFloatNewline(const char *buffer, float *value)
2202 {
2203 int consumed = 0;
2204 sscanf (buffer, "%f\n%n", value, &consumed);
2205 return buffer + consumed;
2206 }
2207
COM_ParseStringNewline(const char * buffer)2208 const char *COM_ParseStringNewline(const char *buffer)
2209 {
2210 int consumed = 0;
2211 com_token[0] = '\0';
2212 sscanf (buffer, "%1023s\n%n", com_token, &consumed);
2213 return buffer + consumed;
2214 }
2215
2216 /*
2217 =================
2218 COM_LoadPackFile -- johnfitz -- modified based on topaz's tutorial
2219
2220 Takes an explicit (not game tree related) path to a pak file.
2221
2222 Loads the header and directory, adding the files at the beginning
2223 of the list so they override previous pack files.
2224 =================
2225 */
COM_LoadPackFile(const char * packfile)2226 static pack_t *COM_LoadPackFile (const char *packfile)
2227 {
2228 dpackheader_t header;
2229 int i;
2230 packfile_t *newfiles;
2231 int numpackfiles;
2232 pack_t *pack;
2233 int packhandle;
2234 dpackfile_t info[MAX_FILES_IN_PACK];
2235 unsigned short crc;
2236
2237 if (Sys_FileOpenRead (packfile, &packhandle) == -1)
2238 return NULL;
2239
2240 Sys_FileRead (packhandle, (void *)&header, sizeof(header));
2241 if (header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K')
2242 Sys_Error ("%s is not a packfile", packfile);
2243
2244 header.dirofs = LittleLong (header.dirofs);
2245 header.dirlen = LittleLong (header.dirlen);
2246
2247 numpackfiles = header.dirlen / sizeof(dpackfile_t);
2248
2249 if (header.dirlen < 0 || header.dirofs < 0)
2250 {
2251 Sys_Error ("Invalid packfile %s (dirlen: %i, dirofs: %i)",
2252 packfile, header.dirlen, header.dirofs);
2253 }
2254 if (!numpackfiles)
2255 {
2256 Sys_Printf ("WARNING: %s has no files, ignored\n", packfile);
2257 Sys_FileClose (packhandle);
2258 return NULL;
2259 }
2260 if (numpackfiles > MAX_FILES_IN_PACK)
2261 Sys_Error ("%s has %i files", packfile, numpackfiles);
2262
2263 if (numpackfiles != PAK0_COUNT)
2264 com_modified = true; // not the original file
2265
2266 newfiles = (packfile_t *) Z_Malloc(numpackfiles * sizeof(packfile_t));
2267
2268 Sys_FileSeek (packhandle, header.dirofs);
2269 Sys_FileRead (packhandle, (void *)info, header.dirlen);
2270
2271 // crc the directory to check for modifications
2272 CRC_Init (&crc);
2273 for (i = 0; i < header.dirlen; i++)
2274 CRC_ProcessByte (&crc, ((byte *)info)[i]);
2275 if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100)
2276 com_modified = true;
2277
2278 // parse the directory
2279 for (i = 0; i < numpackfiles; i++)
2280 {
2281 q_strlcpy (newfiles[i].name, info[i].name, sizeof(newfiles[i].name));
2282 newfiles[i].filepos = LittleLong(info[i].filepos);
2283 newfiles[i].filelen = LittleLong(info[i].filelen);
2284 }
2285
2286 pack = (pack_t *) Z_Malloc (sizeof (pack_t));
2287 q_strlcpy (pack->filename, packfile, sizeof(pack->filename));
2288 pack->handle = packhandle;
2289 pack->numfiles = numpackfiles;
2290 pack->files = newfiles;
2291
2292 //Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
2293 return pack;
2294 }
2295
COM_GetGameNames(qboolean full)2296 const char *COM_GetGameNames(qboolean full)
2297 {
2298 if (full)
2299 {
2300 if (*com_gamenames)
2301 return va("%s;%s", GAMENAME, com_gamenames);
2302 else
2303 return GAMENAME;
2304 }
2305 return com_gamenames;
2306 // return COM_SkipPath(com_gamedir);
2307 }
2308
2309 //if either contain id1 then that gets ignored
COM_GameDirMatches(const char * tdirs)2310 qboolean COM_GameDirMatches(const char *tdirs)
2311 {
2312 int gnl = strlen(GAMENAME);
2313 const char *odirs = COM_GetGameNames(false);
2314
2315 //ignore any core paths.
2316 if (!strncmp(tdirs, GAMENAME, gnl) && (tdirs[gnl] == ';' || !tdirs[gnl]))
2317 {
2318 tdirs+=gnl;
2319 if (*tdirs == ';')
2320 tdirs++;
2321 }
2322 if (!strncmp(odirs, GAMENAME, gnl) && (odirs[gnl] == ';' || !odirs[gnl]))
2323 {
2324 odirs+=gnl;
2325 if (*odirs == ';')
2326 odirs++;
2327 }
2328 //skip any qw in there from quakeworld (remote servers should really be skipping this, unless its maybe the only one in the path).
2329 if (!strncmp(tdirs, "qw;", 3) || !strcmp(tdirs, "qw"))
2330 {
2331 tdirs+=2;
2332 if (*tdirs==';')
2333 tdirs++;
2334 }
2335 if (!strncmp(odirs, "qw;", 3) || !strcmp(odirs, "qw")) //need to cope with ourselves setting it that way too, just in case.
2336 {
2337 odirs+=2;
2338 if (*odirs==';')
2339 odirs++;
2340 }
2341
2342 //okay, now check it properly
2343 if (!strcmp(odirs, tdirs))
2344 return true;
2345 return false;
2346 }
2347
2348 /*
2349 =================
2350 COM_AddGameDirectory -- johnfitz -- modified based on topaz's tutorial
2351 =================
2352 */
COM_AddGameDirectory(const char * dir)2353 static void COM_AddGameDirectory (const char *dir)
2354 {
2355 const char *base = com_basedir;
2356 int i;
2357 unsigned int path_id;
2358 searchpath_t *search;
2359 pack_t *pak, *qspak;
2360 char pakfile[MAX_OSPATH];
2361 qboolean been_here = false;
2362
2363 if (*com_gamenames)
2364 q_strlcat(com_gamenames, ";", sizeof(com_gamenames));
2365 q_strlcat(com_gamenames, dir, sizeof(com_gamenames));
2366
2367 // quakespasm enables mission pack flags automatically,
2368 // so e.g. -game rogue works without breaking the hud
2369 if (!q_strcasecmp(dir,"rogue")) {
2370 rogue = true;
2371 standard_quake = false;
2372 }
2373 if (!q_strcasecmp(dir,"hipnotic") || !q_strcasecmp(dir,"quoth")) {
2374 hipnotic = true;
2375 standard_quake = false;
2376 }
2377
2378 q_strlcpy (com_gamedir, va("%s/%s", base, dir), sizeof(com_gamedir));
2379
2380 // assign a path_id to this game directory
2381 if (com_searchpaths)
2382 path_id = com_searchpaths->path_id << 1;
2383 else path_id = 1U;
2384
2385 _add_path:
2386 // add the directory to the search path
2387 search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t));
2388 search->path_id = path_id;
2389 q_strlcpy (search->filename, com_gamedir, sizeof(search->filename));
2390 search->next = com_searchpaths;
2391 com_searchpaths = search;
2392
2393 // add any pak files in the format pak0.pak pak1.pak, ...
2394 for (i = 0; ; i++)
2395 {
2396 q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i);
2397 pak = COM_LoadPackFile (pakfile);
2398 if (i != 0 || path_id != 1 || fitzmode)
2399 qspak = NULL;
2400 else {
2401 qboolean old = com_modified;
2402 if (been_here) base = host_parms->userdir;
2403 q_snprintf (pakfile, sizeof(pakfile), "%s/vkquake.pak", base);
2404 qspak = COM_LoadPackFile (pakfile);
2405 com_modified = old;
2406 }
2407 if (pak) {
2408 search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t));
2409 search->path_id = path_id;
2410 search->pack = pak;
2411 search->next = com_searchpaths;
2412 com_searchpaths = search;
2413 }
2414 if (qspak) {
2415 search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t));
2416 search->path_id = path_id;
2417 search->pack = qspak;
2418 search->next = com_searchpaths;
2419 com_searchpaths = search;
2420 }
2421 if (!pak) break;
2422 }
2423
2424 if (!been_here && host_parms->userdir != host_parms->basedir)
2425 {
2426 been_here = true;
2427 q_strlcpy(com_gamedir, va("%s/%s", host_parms->userdir, dir), sizeof(com_gamedir));
2428 Sys_mkdir(com_gamedir);
2429 goto _add_path;
2430 }
2431 }
2432
COM_ResetGameDirectories(const char * newdirs)2433 void COM_ResetGameDirectories(const char *newdirs)
2434 {
2435 char *newgamedirs = Z_Strdup(newdirs);
2436 char *newpath, *path;
2437 searchpath_t *search;
2438 //Kill the extra game if it is loaded
2439 while (com_searchpaths != com_base_searchpaths)
2440 {
2441 if (com_searchpaths->pack)
2442 {
2443 Sys_FileClose (com_searchpaths->pack->handle);
2444 Z_Free (com_searchpaths->pack->files);
2445 Z_Free (com_searchpaths->pack);
2446 }
2447 search = com_searchpaths->next;
2448 Z_Free (com_searchpaths);
2449 com_searchpaths = search;
2450 }
2451 hipnotic = false;
2452 rogue = false;
2453 standard_quake = true;
2454 //wipe the list of mod gamedirs
2455 *com_gamenames = 0;
2456 //reset this too
2457 q_strlcpy (com_gamedir, va("%s/%s", (host_parms->userdir != host_parms->basedir)?host_parms->userdir:com_basedir, GAMENAME), sizeof(com_gamedir));
2458
2459 for(newpath = newgamedirs; newpath && *newpath; )
2460 {
2461 char *e = strchr(newpath, ';');
2462 if (e)
2463 *e++ = 0;
2464
2465 if (!q_strcasecmp(GAMENAME, newpath))
2466 path = NULL;
2467 else
2468 {
2469 for (path = newgamedirs; path < newpath; path += strlen(path)+1)
2470 {
2471 if (!q_strcasecmp(path, newpath))
2472 break;
2473 }
2474 }
2475
2476 if (path == newpath) //not already loaded
2477 COM_AddGameDirectory(newpath);
2478 newpath = e;
2479 }
2480 Z_Free(newgamedirs);
2481 }
2482
2483 //==============================================================================
2484 //johnfitz -- dynamic gamedir stuff -- modified by QuakeSpasm team.
2485 //==============================================================================
COM_Game_f(void)2486 static void COM_Game_f (void)
2487 {
2488 if (Cmd_Argc() > 1)
2489 {
2490 int i, pri;
2491 char paths[1024];
2492
2493 if (!registered.value) //disable shareware quake
2494 {
2495 Con_Printf("You must have the registered version to use modified games\n");
2496 return;
2497 }
2498
2499 *paths = 0;
2500 q_strlcat(paths, GAMENAME, sizeof(paths));
2501 for (pri = 0; pri <= 1; pri++)
2502 {
2503 for (i = 1; i < Cmd_Argc(); i++)
2504 {
2505 const char *p = Cmd_Argv(i);
2506 if (!*p)
2507 p = GAMENAME;
2508 if (pri == 0)
2509 {
2510 if (*p != '-')
2511 continue;
2512 p++;
2513 }
2514 else if (*p == '-')
2515 continue;
2516
2517 if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":"))
2518 {
2519 Con_Printf ("gamedir should be a single directory name, not a path\n");
2520 return;
2521 }
2522
2523 if (!q_strcasecmp(p, GAMENAME))
2524 continue; //don't add id1, its not interesting enough.
2525
2526 if (*paths)
2527 q_strlcat(paths, ";", sizeof(paths));
2528 q_strlcat(paths, p, sizeof(paths));
2529 }
2530 }
2531
2532 if (!q_strcasecmp(paths, COM_GetGameNames(true)))
2533 {
2534 Con_Printf("\"game\" is already \"%s\"\n", COM_GetGameNames(true));
2535 return;
2536 }
2537
2538 com_modified = true;
2539
2540 //Kill the server
2541 CL_Disconnect ();
2542 Host_ShutdownServer(true);
2543
2544 //Write config file
2545 Host_WriteConfiguration ();
2546
2547 COM_ResetGameDirectories(paths);
2548
2549 //clear out and reload appropriate data
2550 Cache_Flush ();
2551 Mod_ResetAll();
2552 Sky_ClearAll();
2553 if (!isDedicated)
2554 {
2555 TexMgr_NewGame ();
2556 Draw_NewGame ();
2557 R_NewGame ();
2558 }
2559 ExtraMaps_NewGame ();
2560 DemoList_Rebuild ();
2561
2562 Con_Printf("\"game\" changed to \"%s\"\n", COM_GetGameNames(true));
2563
2564 VID_Lock ();
2565 Cbuf_AddText ("exec quake.rc\n");
2566 Cbuf_AddText ("vid_unlock\n");
2567 }
2568 else //Diplay the current gamedir
2569 Con_Printf("\"game\" is \"%s\"\n", COM_GetGameNames(true));
2570 }
2571
2572 /*
2573 =================
2574 COM_InitFilesystem
2575 =================
2576 */
COM_InitFilesystem(void)2577 void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial
2578 {
2579 int i, j;
2580 const char *p;
2581
2582 Cvar_RegisterVariable (®istered);
2583 Cvar_RegisterVariable (&cmdline);
2584 Cmd_AddCommand ("path", COM_Path_f);
2585 Cmd_AddCommand ("game", COM_Game_f); //johnfitz
2586
2587 i = COM_CheckParm ("-basedir");
2588 if (i && i < com_argc-1)
2589 q_strlcpy (com_basedir, com_argv[i + 1], sizeof(com_basedir));
2590 else
2591 q_strlcpy (com_basedir, DATADIR, sizeof(com_basedir));
2592
2593 j = strlen (com_basedir);
2594 if (j < 1) Sys_Error("Bad argument to -basedir");
2595 if ((com_basedir[j-1] == '\\') || (com_basedir[j-1] == '/'))
2596 com_basedir[j-1] = 0;
2597
2598 i = COM_CheckParmNext (i, "-basegame");
2599 if (i)
2600 { //-basegame:
2601 // a) replaces all hardcoded dirs (read: alternative to id1)
2602 // b) isn't flushed on normal gamedir switches (like id1).
2603 com_modified = true; //shouldn't be relevant when not using id content... but we don't really know.
2604 for(;; i = COM_CheckParmNext (i, "-basegame"))
2605 {
2606 if (!i || i >= com_argc-1)
2607 break;
2608
2609 p = com_argv[i + 1];
2610 if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":"))
2611 Sys_Error ("gamedir should be a single directory name, not a path\n");
2612 if (p != NULL)
2613 COM_AddGameDirectory (p);
2614 }
2615 }
2616 else
2617 {
2618 // start up with GAMENAME by default (id1)
2619 COM_AddGameDirectory (GAMENAME);
2620 }
2621
2622 /* this is the end of our base searchpath:
2623 * any set gamedirs, such as those from -game command line
2624 * arguments or by the 'game' console command will be freed
2625 * up to here upon a new game command. */
2626 com_base_searchpaths = com_searchpaths;
2627 COM_ResetGameDirectories("");
2628
2629 // add mission pack requests (only one should be specified)
2630 if (COM_CheckParm ("-rogue"))
2631 COM_AddGameDirectory ("rogue");
2632 if (COM_CheckParm ("-hipnotic"))
2633 COM_AddGameDirectory ("hipnotic");
2634 if (COM_CheckParm ("-quoth"))
2635 COM_AddGameDirectory ("quoth");
2636
2637
2638 for(i = 0;;)
2639 {
2640 i = COM_CheckParmNext (i, "-game");
2641 if (!i || i >= com_argc-1)
2642 break;
2643
2644 p = com_argv[i + 1];
2645 if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":"))
2646 Sys_Error ("gamedir should be a single directory name, not a path\n");
2647 com_modified = true;
2648 if (p != NULL)
2649 COM_AddGameDirectory (p);
2650 }
2651
2652 if (COM_CheckParm ("-validation"))
2653 vulkan_globals.validation = true;
2654
2655 COM_CheckRegistered ();
2656 }
2657
2658
2659 /* The following FS_*() stdio replacements are necessary if one is
2660 * to perform non-sequential reads on files reopened on pak files
2661 * because we need the bookkeeping about file start/end positions.
2662 * Allocating and filling in the fshandle_t structure is the users'
2663 * responsibility when the file is initially opened. */
2664
FS_fread(void * ptr,size_t size,size_t nmemb,fshandle_t * fh)2665 size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh)
2666 {
2667 long byte_size;
2668 long bytes_read;
2669 size_t nmemb_read;
2670
2671 if (!fh) {
2672 errno = EBADF;
2673 return 0;
2674 }
2675 if (!ptr) {
2676 errno = EFAULT;
2677 return 0;
2678 }
2679 if (!size || !nmemb) { /* no error, just zero bytes wanted */
2680 errno = 0;
2681 return 0;
2682 }
2683
2684 byte_size = nmemb * size;
2685 if (byte_size > fh->length - fh->pos) /* just read to end */
2686 byte_size = fh->length - fh->pos;
2687 bytes_read = fread(ptr, 1, byte_size, fh->file);
2688 fh->pos += bytes_read;
2689
2690 /* fread() must return the number of elements read,
2691 * not the total number of bytes. */
2692 nmemb_read = bytes_read / size;
2693 /* even if the last member is only read partially
2694 * it is counted as a whole in the return value. */
2695 if (bytes_read % size)
2696 nmemb_read++;
2697
2698 return nmemb_read;
2699 }
2700
FS_fseek(fshandle_t * fh,long offset,int whence)2701 int FS_fseek(fshandle_t *fh, long offset, int whence)
2702 {
2703 /* I don't care about 64 bit off_t or fseeko() here.
2704 * the quake/hexen2 file system is 32 bits, anyway. */
2705 int ret;
2706
2707 if (!fh) {
2708 errno = EBADF;
2709 return -1;
2710 }
2711
2712 /* the relative file position shouldn't be smaller
2713 * than zero or bigger than the filesize. */
2714 switch (whence)
2715 {
2716 case SEEK_SET:
2717 break;
2718 case SEEK_CUR:
2719 offset += fh->pos;
2720 break;
2721 case SEEK_END:
2722 offset = fh->length + offset;
2723 break;
2724 default:
2725 errno = EINVAL;
2726 return -1;
2727 }
2728
2729 if (offset < 0) {
2730 errno = EINVAL;
2731 return -1;
2732 }
2733
2734 if (offset > fh->length) /* just seek to end */
2735 offset = fh->length;
2736
2737 ret = fseek(fh->file, fh->start + offset, SEEK_SET);
2738 if (ret < 0)
2739 return ret;
2740
2741 fh->pos = offset;
2742 return 0;
2743 }
2744
FS_fclose(fshandle_t * fh)2745 int FS_fclose(fshandle_t *fh)
2746 {
2747 if (!fh) {
2748 errno = EBADF;
2749 return -1;
2750 }
2751 return fclose(fh->file);
2752 }
2753
FS_ftell(fshandle_t * fh)2754 long FS_ftell(fshandle_t *fh)
2755 {
2756 if (!fh) {
2757 errno = EBADF;
2758 return -1;
2759 }
2760 return fh->pos;
2761 }
2762
FS_rewind(fshandle_t * fh)2763 void FS_rewind(fshandle_t *fh)
2764 {
2765 if (!fh) return;
2766 clearerr(fh->file);
2767 fseek(fh->file, fh->start, SEEK_SET);
2768 fh->pos = 0;
2769 }
2770
FS_feof(fshandle_t * fh)2771 int FS_feof(fshandle_t *fh)
2772 {
2773 if (!fh) {
2774 errno = EBADF;
2775 return -1;
2776 }
2777 if (fh->pos >= fh->length)
2778 return -1;
2779 return 0;
2780 }
2781
FS_ferror(fshandle_t * fh)2782 int FS_ferror(fshandle_t *fh)
2783 {
2784 if (!fh) {
2785 errno = EBADF;
2786 return -1;
2787 }
2788 return ferror(fh->file);
2789 }
2790
FS_fgetc(fshandle_t * fh)2791 int FS_fgetc(fshandle_t *fh)
2792 {
2793 if (!fh) {
2794 errno = EBADF;
2795 return EOF;
2796 }
2797 if (fh->pos >= fh->length)
2798 return EOF;
2799 fh->pos += 1;
2800 return fgetc(fh->file);
2801 }
2802
FS_fgets(char * s,int size,fshandle_t * fh)2803 char *FS_fgets(char *s, int size, fshandle_t *fh)
2804 {
2805 char *ret;
2806
2807 if (FS_feof(fh))
2808 return NULL;
2809
2810 if (size > (fh->length - fh->pos) + 1)
2811 size = (fh->length - fh->pos) + 1;
2812
2813 ret = fgets(s, size, fh->file);
2814 fh->pos = ftell(fh->file) - fh->start;
2815
2816 return ret;
2817 }
2818
FS_filelength(fshandle_t * fh)2819 long FS_filelength (fshandle_t *fh)
2820 {
2821 if (!fh) {
2822 errno = EBADF;
2823 return -1;
2824 }
2825 return fh->length;
2826 }
2827
2828 #ifdef PSET_SCRIPT
2829 //for compat with dpp7 protocols, and mods that cba to precache things.
COM_Effectinfo_Enumerate(int (* cb)(const char * pname))2830 void COM_Effectinfo_Enumerate(int (*cb)(const char *pname))
2831 {
2832 int i;
2833 const char *f, *e;
2834 char *buf;
2835 static const char *dpnames[] =
2836 {
2837 "TE_GUNSHOT",
2838 "TE_GUNSHOTQUAD",
2839 "TE_SPIKE",
2840 "TE_SPIKEQUAD",
2841 "TE_SUPERSPIKE",
2842 "TE_SUPERSPIKEQUAD",
2843 "TE_WIZSPIKE",
2844 "TE_KNIGHTSPIKE",
2845 "TE_EXPLOSION",
2846 "TE_EXPLOSIONQUAD",
2847 "TE_TAREXPLOSION",
2848 "TE_TELEPORT",
2849 "TE_LAVASPLASH",
2850 "TE_SMALLFLASH",
2851 "TE_FLAMEJET",
2852 "EF_FLAME",
2853 "TE_BLOOD",
2854 "TE_SPARK",
2855 "TE_PLASMABURN",
2856 "TE_TEI_G3",
2857 "TE_TEI_SMOKE",
2858 "TE_TEI_BIGEXPLOSION",
2859 "TE_TEI_PLASMAHIT",
2860 "EF_STARDUST",
2861 "TR_ROCKET",
2862 "TR_GRENADE",
2863 "TR_BLOOD",
2864 "TR_WIZSPIKE",
2865 "TR_SLIGHTBLOOD",
2866 "TR_KNIGHTSPIKE",
2867 "TR_VORESPIKE",
2868 "TR_NEHAHRASMOKE",
2869 "TR_NEXUIZPLASMA",
2870 "TR_GLOWTRAIL",
2871 "SVC_PARTICLE",
2872 NULL
2873 };
2874
2875 buf = (char*)COM_LoadMallocFile("effectinfo.txt", NULL);
2876 if (!buf)
2877 return;
2878
2879 for (i = 0; dpnames[i]; i++)
2880 cb(dpnames[i]);
2881
2882 for (f = buf; f; f = e)
2883 {
2884 e = COM_Parse (f);
2885 if (!strcmp(com_token, "effect"))
2886 {
2887 e = COM_Parse (e);
2888 cb(com_token);
2889 }
2890 while (e && *e && *e != '\n')
2891 e++;
2892 }
2893 free(buf);
2894 }
2895 #endif
2896
2897 /*
2898 ============================================================================
2899 LOCALIZATION
2900 ============================================================================
2901 */
2902 typedef struct
2903 {
2904 char *key;
2905 char *value;
2906 } locentry_t;
2907
2908 typedef struct
2909 {
2910 int numentries;
2911 int maxnumentries;
2912 int numindices;
2913 unsigned *indices;
2914 locentry_t *entries;
2915 char *text;
2916 } localization_t;
2917
2918 static localization_t localization;
2919
2920 /*
2921 ================
2922 COM_HashString
2923 Computes the FNV-1a hash of string str
2924 ================
2925 */
COM_HashString(const char * str)2926 unsigned COM_HashString (const char *str)
2927 {
2928 unsigned hash = 0x811c9dc5u;
2929 while (*str)
2930 {
2931 hash ^= *str++;
2932 hash *= 0x01000193u;
2933 }
2934 return hash;
2935 }
2936
mz_zip_file_read_func(void * opaque,mz_uint64 ofs,void * buf,size_t n)2937 static size_t mz_zip_file_read_func(void *opaque, mz_uint64 ofs, void *buf, size_t n)
2938 {
2939 if (SDL_RWseek((SDL_RWops*)opaque, (Sint64)ofs, RW_SEEK_SET) < 0)
2940 return 0;
2941 return SDL_RWread((SDL_RWops*)opaque, buf, 1, n);
2942 }
2943
2944 /*
2945 ================
2946 LOC_LoadFile
2947 ================
2948 */
LOC_LoadFile(const char * file)2949 void LOC_LoadFile (const char *file)
2950 {
2951 char path[1024];
2952 int i,lineno;
2953 char *cursor;
2954
2955 SDL_RWops *rw = NULL;
2956 Sint64 sz;
2957 mz_zip_archive archive;
2958 size_t size = 0;
2959
2960 // clear existing data
2961 if (localization.text)
2962 {
2963 free(localization.text);
2964 localization.text = NULL;
2965 }
2966 localization.numentries = 0;
2967 localization.numindices = 0;
2968
2969 if (!file || !*file)
2970 return;
2971
2972 Con_Printf("\nLanguage initialization\n");
2973
2974 memset(&archive, 0, sizeof(archive));
2975 q_snprintf(path, sizeof(path), "%s/%s", com_basedir, file);
2976 rw = SDL_RWFromFile(path, "rb");
2977 #if defined(DO_USERDIRS)
2978 if (!rw) {
2979 q_snprintf(path, sizeof(path), "%s/%s", host_parms->userdir, file);
2980 rw = SDL_RWFromFile(path, "rb");
2981 }
2982 #endif
2983 if (!rw)
2984 {
2985 q_snprintf(path, sizeof(path), "%s/QuakeEX.kpf", com_basedir);
2986 rw = SDL_RWFromFile(path, "rb");
2987 #if defined(DO_USERDIRS)
2988 if (!rw) {
2989 q_snprintf(path, sizeof(path), "%s/QuakeEX.kpf", host_parms->userdir);
2990 rw = SDL_RWFromFile(path, "rb");
2991 }
2992 #endif
2993 if (!rw) goto fail;
2994 sz = SDL_RWsize(rw);
2995 if (sz <= 0) goto fail;
2996 archive.m_pRead = mz_zip_file_read_func;
2997 archive.m_pIO_opaque = rw;
2998 if (!mz_zip_reader_init(&archive, sz, 0)) goto fail;
2999 localization.text = (char *) mz_zip_reader_extract_file_to_heap(&archive, file, &size, 0);
3000 if (!localization.text) goto fail;
3001 mz_zip_reader_end(&archive);
3002 SDL_RWclose(rw);
3003 localization.text = (char *) realloc(localization.text, size+1);
3004 localization.text[size] = 0;
3005 }
3006 else
3007 {
3008 sz = SDL_RWsize(rw);
3009 if (sz <= 0) goto fail;
3010 localization.text = (char *) calloc(1, sz+1);
3011 if (!localization.text)
3012 {
3013 fail: mz_zip_reader_end(&archive);
3014 if (rw) SDL_RWclose(rw);
3015 Con_Printf("Couldn't load '%s'\nfrom '%s'\n", file, com_basedir);
3016 return;
3017 }
3018 SDL_RWread(rw, localization.text, 1, sz);
3019 SDL_RWclose(rw);
3020 }
3021
3022 cursor = localization.text;
3023
3024 // skip BOM
3025 if ((unsigned char)(cursor[0]) == 0xEF && (unsigned char)(cursor[1]) == 0xBB && cursor[2] == 0xB)
3026 cursor += 3;
3027
3028 lineno = 0;
3029 while (*cursor)
3030 {
3031 char *line, *equals;
3032
3033 lineno++;
3034
3035 // skip leading whitespace
3036 while (q_isblank(*cursor))
3037 ++cursor;
3038
3039 line = cursor;
3040 equals = NULL;
3041 // find line end and first equals sign, if any
3042 while (*cursor && *cursor != '\n')
3043 {
3044 if (*cursor == '=' && !equals)
3045 equals = cursor;
3046 cursor++;
3047 }
3048
3049 if (line[0] == '/')
3050 {
3051 if (line[1] != '/')
3052 Con_DPrintf("LOC_LoadFile: malformed comment on line %d\n", lineno);
3053 }
3054 else if (equals)
3055 {
3056 char *key_end = equals;
3057 qboolean leading_quote;
3058 qboolean trailing_quote;
3059 locentry_t *entry;
3060 char *value_src;
3061 char *value_dst;
3062 char *value;
3063
3064 // trim whitespace before equals sign
3065 while (key_end != line && q_isspace(key_end[-1]))
3066 key_end--;
3067 *key_end = 0;
3068
3069 value = equals + 1;
3070 // skip whitespace after equals sign
3071 while (value != cursor && q_isspace(*value))
3072 value++;
3073
3074 leading_quote = (*value == '\"');
3075 trailing_quote = false;
3076 value += leading_quote;
3077
3078 // transform escape sequences in-place
3079 value_src = value;
3080 value_dst = value;
3081 while (value_src != cursor)
3082 {
3083 if (*value_src == '\\' && value_src + 1 != cursor)
3084 {
3085 char c = value_src[1];
3086 value_src += 2;
3087 switch (c)
3088 {
3089 case 'n': *value_dst++ = '\n'; break;
3090 case 't': *value_dst++ = '\t'; break;
3091 case 'v': *value_dst++ = '\v'; break;
3092 case 'b': *value_dst++ = '\b'; break;
3093 case 'f': *value_dst++ = '\f'; break;
3094
3095 case '"':
3096 case '\'':
3097 *value_dst++ = c;
3098 break;
3099
3100 default:
3101 Con_Printf("LOC_LoadFile: unrecognized escape sequence \\%c on line %d\n", c, lineno);
3102 *value_dst++ = c;
3103 break;
3104 }
3105 continue;
3106 }
3107
3108 if (*value_src == '\"')
3109 {
3110 trailing_quote = true;
3111 *value_dst = 0;
3112 break;
3113 }
3114
3115 *value_dst++ = *value_src++;
3116 }
3117
3118 // if not a quoted string, trim trailing whitespace
3119 if (!trailing_quote)
3120 {
3121 while (value_dst != value && q_isblank(value_dst[-1]))
3122 {
3123 *value_dst = 0;
3124 value_dst--;
3125 }
3126 }
3127
3128 if (localization.numentries == localization.maxnumentries)
3129 {
3130 // grow by 50%
3131 localization.maxnumentries += localization.maxnumentries >> 1;
3132 localization.maxnumentries = q_max(localization.maxnumentries, 32);
3133 localization.entries = (locentry_t*) realloc(localization.entries, sizeof(*localization.entries) * localization.maxnumentries);
3134 }
3135
3136 entry = &localization.entries[localization.numentries++];
3137 entry->key = line;
3138 entry->value = value;
3139 }
3140
3141 if (*cursor)
3142 *cursor++ = 0; // terminate line and advance to next
3143 }
3144
3145 // hash all entries
3146
3147 localization.numindices = localization.numentries * 2; // 50% load factor
3148 if (localization.numindices == 0)
3149 {
3150 Con_Printf("No localized strings in file '%s'\n", file);
3151 return;
3152 }
3153
3154 localization.indices = (unsigned*) realloc(localization.indices, localization.numindices * sizeof(*localization.indices));
3155 memset(localization.indices, 0, localization.numindices * sizeof(*localization.indices));
3156
3157 for (i = 0; i < localization.numentries; i++)
3158 {
3159 locentry_t *entry = &localization.entries[i];
3160 unsigned pos = COM_HashString(entry->key) % localization.numindices, end = pos;
3161
3162 for (;;)
3163 {
3164 if (!localization.indices[pos])
3165 {
3166 localization.indices[pos] = i + 1;
3167 break;
3168 }
3169
3170 ++pos;
3171 if (pos == localization.numindices)
3172 pos = 0;
3173
3174 if (pos == end)
3175 Sys_Error("LOC_LoadFile failed");
3176 }
3177 }
3178
3179 Con_Printf("Loaded %d strings from '%s'\n", localization.numentries, file);
3180 }
3181
3182 /*
3183 ================
3184 LOC_Init
3185 ================
3186 */
LOC_Init(void)3187 void LOC_Init(void)
3188 {
3189 LOC_LoadFile("localization/loc_english.txt");
3190 }
3191
3192 /*
3193 ================
3194 LOC_Shutdown
3195 ================
3196 */
LOC_Shutdown(void)3197 void LOC_Shutdown(void)
3198 {
3199 free(localization.indices);
3200 free(localization.entries);
3201 free(localization.text);
3202 }
3203
3204 /*
3205 ================
3206 LOC_GetRawString
3207
3208 Returns localized string if available, or NULL otherwise
3209 ================
3210 */
LOC_GetRawString(const char * key)3211 const char* LOC_GetRawString (const char *key)
3212 {
3213 unsigned pos, end;
3214
3215 if (!localization.numindices || !key || !*key || *key != '$')
3216 return NULL;
3217 key++;
3218
3219 pos = COM_HashString(key) % localization.numindices;
3220 end = pos;
3221
3222 do
3223 {
3224 unsigned idx = localization.indices[pos];
3225 locentry_t *entry;
3226 if (!idx)
3227 return NULL;
3228
3229 entry = &localization.entries[idx - 1];
3230 if (!Q_strcmp(entry->key, key))
3231 return entry->value;
3232
3233 ++pos;
3234 if (pos == localization.numindices)
3235 pos = 0;
3236 } while (pos != end);
3237
3238 return NULL;
3239 }
3240
3241 /*
3242 ================
3243 LOC_GetString
3244
3245 Returns localized string if available, or input string otherwise
3246 ================
3247 */
LOC_GetString(const char * key)3248 const char* LOC_GetString (const char *key)
3249 {
3250 const char* value = LOC_GetRawString(key);
3251 return value ? value : key;
3252 }
3253
3254 /*
3255 ================
3256 LOC_ParseArg
3257
3258 Returns argument index (>= 0) and advances the string if it starts with a placeholder ({} or {N}),
3259 otherwise returns a negative value and leaves the pointer unchanged
3260 ================
3261 */
LOC_ParseArg(const char ** pstr)3262 static int LOC_ParseArg (const char **pstr)
3263 {
3264 int arg;
3265 const char *str = *pstr;
3266
3267 // opening brace
3268 if (*str != '{')
3269 return -1;
3270 ++str;
3271
3272 // optional index, defaulting to 0
3273 arg = 0;
3274 while (q_isdigit(*str))
3275 arg = arg * 10 + *str++ - '0';
3276
3277 // closing brace
3278 if (*str != '}')
3279 return -1;
3280 *pstr = ++str;
3281
3282 return arg;
3283 }
3284
3285 /*
3286 ================
3287 LOC_HasPlaceholders
3288 ================
3289 */
LOC_HasPlaceholders(const char * str)3290 qboolean LOC_HasPlaceholders (const char *str)
3291 {
3292 if (!localization.numindices)
3293 return false;
3294 while (*str)
3295 {
3296 if (LOC_ParseArg(&str) >= 0)
3297 return true;
3298 str++;
3299 }
3300 return false;
3301 }
3302
3303 /*
3304 ================
3305 LOC_Format
3306
3307 Replaces placeholders (of the form {} or {N}) with the corresponding arguments
3308
3309 Returns number of written chars, excluding the NUL terminator
3310 If len > 0, output is always NUL-terminated
3311 ================
3312 */
LOC_Format(const char * format,const char * (* getarg_fn)(int idx,void * userdata),void * userdata,char * out,size_t len)3313 size_t LOC_Format (const char *format, const char* (*getarg_fn) (int idx, void* userdata), void* userdata, char* out, size_t len)
3314 {
3315 size_t written = 0;
3316 int numargs = 0;
3317
3318 if (!len)
3319 {
3320 Con_DPrintf("LOC_Format: no output space\n");
3321 return 0;
3322 }
3323 --len; // reserve space for the terminator
3324
3325 while (*format && written < len)
3326 {
3327 const char* insert;
3328 size_t space_left;
3329 size_t insert_len;
3330 int argindex = LOC_ParseArg(&format);
3331
3332 if (argindex < 0)
3333 {
3334 out[written++] = *format++;
3335 continue;
3336 }
3337
3338 insert = getarg_fn(argindex, userdata);
3339 space_left = len - written;
3340 insert_len = Q_strlen(insert);
3341
3342 if (insert_len > space_left)
3343 {
3344 Con_DPrintf("LOC_Format: overflow at argument #%d\n", numargs);
3345 insert_len = space_left;
3346 }
3347
3348 Q_memcpy(out + written, insert, insert_len);
3349 written += insert_len;
3350 }
3351
3352 if (*format)
3353 Con_DPrintf("LOC_Format: overflow\n");
3354
3355 out[written] = 0;
3356
3357 return written;
3358 }
3359