1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file m_misc.h
12 /// \brief Commonly used routines
13 /// Default config file, PCX screenshots, file i/o
14
15 #ifdef __GNUC__
16
17 #if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
18 // Ignore "argument might be clobbered by longjmp" warning in GCC
19 // (if libpng is compiled with setjmp error handling)
20 #pragma GCC diagnostic ignored "-Wclobbered"
21 #endif
22
23 #include <unistd.h>
24 #endif
25
26 #include <errno.h>
27
28 // Extended map support.
29 #include <ctype.h>
30
31 #include "doomdef.h"
32 #include "g_game.h"
33 #include "m_misc.h"
34 #include "hu_stuff.h"
35 #include "st_stuff.h"
36 #include "v_video.h"
37 #include "z_zone.h"
38 #include "g_input.h"
39 #include "i_video.h"
40 #include "d_main.h"
41 #include "m_argv.h"
42 #include "i_system.h"
43 #include "command.h" // cv_execversion
44
45 #include "m_anigif.h"
46
47 // So that the screenshot menu auto-updates...
48 #include "m_menu.h"
49
50 #ifdef HWRENDER
51 #include "hardware/hw_main.h"
52 #endif
53
54 #ifdef HAVE_SDL
55 #include "sdl/hwsym_sdl.h"
56 #ifdef __linux__
57 #ifndef _LARGEFILE64_SOURCE
58 typedef off_t off64_t;
59 #endif
60 #endif
61 #endif
62
63 #if defined(__MINGW32__) && ((__GNUC__ > 7) || (__GNUC__ == 6 && __GNUC_MINOR__ >= 3)) && (__GNUC__ < 8)
64 #define PRIdS "u"
65 #elif defined (_WIN32)
66 #define PRIdS "Iu"
67 #elif defined (DJGPP)
68 #define PRIdS "u"
69 #else
70 #define PRIdS "zu"
71 #endif
72
73 #ifdef HAVE_PNG
74
75 #ifndef _MSC_VER
76 #ifndef _LARGEFILE64_SOURCE
77 #define _LARGEFILE64_SOURCE
78 #endif
79 #endif
80
81 #ifndef _LFS64_LARGEFILE
82 #define _LFS64_LARGEFILE
83 #endif
84
85 #ifndef _FILE_OFFSET_BITS
86 #define _FILE_OFFSET_BITS 0
87 #endif
88
89 #include "zlib.h"
90 #include "png.h"
91 #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 4)
92 #define NO_PNG_DEBUG // 1.4.0 move png_debug to pngpriv.h
93 #endif
94 #ifdef PNG_WRITE_SUPPORTED
95 #define USE_PNG // Only actually use PNG if write is supported.
96 #if defined (PNG_WRITE_APNG_SUPPORTED) //|| !defined(PNG_STATIC)
97 #include "apng.h"
98 #define USE_APNG
99 #endif
100 // See hardware/hw_draw.c for a similar check to this one.
101 #endif
102 #endif
103
104 static CV_PossibleValue_t screenshot_cons_t[] = {{0, "Default"}, {1, "HOME"}, {2, "SRB2"}, {3, "CUSTOM"}, {0, NULL}};
105 consvar_t cv_screenshot_option = CVAR_INIT ("screenshot_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Screenshot_option_Onchange);
106 consvar_t cv_screenshot_folder = CVAR_INIT ("screenshot_folder", "", CV_SAVE, NULL, NULL);
107
108 consvar_t cv_screenshot_colorprofile = CVAR_INIT ("screenshot_colorprofile", "Yes", CV_SAVE, CV_YesNo, NULL);
109
110 static CV_PossibleValue_t moviemode_cons_t[] = {{MM_GIF, "GIF"}, {MM_APNG, "aPNG"}, {MM_SCREENSHOT, "Screenshots"}, {0, NULL}};
111 consvar_t cv_moviemode = CVAR_INIT ("moviemode_mode", "GIF", CV_SAVE|CV_CALL, moviemode_cons_t, Moviemode_mode_Onchange);
112
113 consvar_t cv_movie_option = CVAR_INIT ("movie_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Moviemode_option_Onchange);
114 consvar_t cv_movie_folder = CVAR_INIT ("movie_folder", "", CV_SAVE, NULL, NULL);
115
116 static CV_PossibleValue_t zlib_mem_level_t[] = {
117 {1, "(Min Memory) 1"},
118 {2, "2"}, {3, "3"}, {4, "4"}, {5, "5"}, {6, "6"}, {7, "7"},
119 {8, "(Optimal) 8"}, //libpng Default
120 {9, "(Max Memory) 9"}, {0, NULL}};
121
122 static CV_PossibleValue_t zlib_level_t[] = {
123 {0, "No Compression"}, //Z_NO_COMPRESSION
124 {1, "(Fastest) 1"}, //Z_BEST_SPEED
125 {2, "2"}, {3, "3"}, {4, "4"}, {5, "5"},
126 {6, "(Optimal) 6"}, //Zlib Default
127 {7, "7"}, {8, "8"},
128 {9, "(Maximum) 9"}, //Z_BEST_COMPRESSION
129 {0, NULL}};
130
131 static CV_PossibleValue_t zlib_strategy_t[] = {
132 {0, "Normal"}, //Z_DEFAULT_STRATEGY
133 {1, "Filtered"}, //Z_FILTERED
134 {2, "Huffman Only"}, //Z_HUFFMAN_ONLY
135 {3, "RLE"}, //Z_RLE
136 {4, "Fixed"}, //Z_FIXED
137 {0, NULL}};
138
139 static CV_PossibleValue_t zlib_window_bits_t[] = {
140 #ifdef WBITS_8_OK
141 {8, "256"},
142 #endif
143 {9, "512"}, {10, "1k"}, {11, "2k"}, {12, "4k"}, {13, "8k"},
144 {14, "16k"}, {15, "32k"},
145 {0, NULL}};
146
147 static CV_PossibleValue_t apng_delay_t[] = {
148 {1, "1x"},
149 {2, "1/2x"},
150 {3, "1/3x"},
151 {4, "1/4x"},
152 {0, NULL}};
153
154 // zlib memory usage is as follows:
155 // (1 << (zlib_window_bits+2)) + (1 << (zlib_level+9))
156 consvar_t cv_zlib_memory = CVAR_INIT ("png_memory_level", "7", CV_SAVE, zlib_mem_level_t, NULL);
157 consvar_t cv_zlib_level = CVAR_INIT ("png_compress_level", "(Optimal) 6", CV_SAVE, zlib_level_t, NULL);
158 consvar_t cv_zlib_strategy = CVAR_INIT ("png_strategy", "Normal", CV_SAVE, zlib_strategy_t, NULL);
159 consvar_t cv_zlib_window_bits = CVAR_INIT ("png_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL);
160
161 consvar_t cv_zlib_memorya = CVAR_INIT ("apng_memory_level", "(Max Memory) 9", CV_SAVE, zlib_mem_level_t, NULL);
162 consvar_t cv_zlib_levela = CVAR_INIT ("apng_compress_level", "4", CV_SAVE, zlib_level_t, NULL);
163 consvar_t cv_zlib_strategya = CVAR_INIT ("apng_strategy", "RLE", CV_SAVE, zlib_strategy_t, NULL);
164 consvar_t cv_zlib_window_bitsa = CVAR_INIT ("apng_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL);
165 consvar_t cv_apng_delay = CVAR_INIT ("apng_speed", "1x", CV_SAVE, apng_delay_t, NULL);
166 consvar_t cv_apng_downscale = CVAR_INIT ("apng_downscale", "On", CV_SAVE, CV_OnOff, NULL);
167
168 #ifdef USE_APNG
169 static boolean apng_downscale = false; // So nobody can do something dumb like changing cvars mid output
170 #endif
171
172 boolean takescreenshot = false; // Take a screenshot this tic
173
174 moviemode_t moviemode = MM_OFF;
175
176 /** Returns the map number for a map identified by the last two characters in
177 * its name.
178 *
179 * \param first The first character after MAP.
180 * \param second The second character after MAP.
181 * \return The map number, or 0 if no map corresponds to these characters.
182 * \sa G_BuildMapName
183 */
M_MapNumber(char first,char second)184 INT32 M_MapNumber(char first, char second)
185 {
186 if (isdigit(first))
187 {
188 if (isdigit(second))
189 return ((INT32)first - '0') * 10 + ((INT32)second - '0');
190 return 0;
191 }
192
193 if (!isalpha(first))
194 return 0;
195 if (!isalnum(second))
196 return 0;
197
198 return 100 + ((INT32)tolower(first) - 'a') * 36 + (isdigit(second) ? ((INT32)second - '0') :
199 ((INT32)tolower(second) - 'a') + 10);
200 }
201
202 // ==========================================================================
203 // FILE INPUT / OUTPUT
204 // ==========================================================================
205
206 // some libcs has no access function, make our own
207 #if 0
208 int access(const char *path, int amode)
209 {
210 int accesshandle = -1;
211 FILE *handle = NULL;
212 if (amode == 6) // W_OK|R_OK
213 handle = fopen(path, "r+");
214 else if (amode == 4) // R_OK
215 handle = fopen(path, "r");
216 else if (amode == 2) // W_OK
217 handle = fopen(path, "a+");
218 else if (amode == 0) //F_OK
219 handle = fopen(path, "rb");
220 if (handle)
221 {
222 accesshandle = 0;
223 fclose(handle);
224 }
225 return accesshandle;
226 }
227 #endif
228
229
230 //
231 // FIL_WriteFile
232 //
233 #ifndef O_BINARY
234 #define O_BINARY 0
235 #endif
236
237 /** Writes out a file.
238 *
239 * \param name Name of the file to write.
240 * \param source Memory location to write from.
241 * \param length How many bytes to write.
242 * \return True on success, false on failure.
243 */
FIL_WriteFile(char const * name,const void * source,size_t length)244 boolean FIL_WriteFile(char const *name, const void *source, size_t length)
245 {
246 FILE *handle = NULL;
247 size_t count;
248
249 //if (FIL_WriteFileOK(name))
250 handle = fopen(name, "w+b");
251
252 if (!handle)
253 return false;
254
255 count = fwrite(source, 1, length, handle);
256 fclose(handle);
257
258 if (count < length)
259 return false;
260
261 return true;
262 }
263
264 /** Reads in a file, appending a zero byte at the end.
265 *
266 * \param name Filename to read.
267 * \param buffer Pointer to a pointer, which will be set to the location of a
268 * newly allocated buffer holding the file's contents.
269 * \return Number of bytes read, not counting the zero byte added to the end,
270 * or 0 on error.
271 */
FIL_ReadFileTag(char const * name,UINT8 ** buffer,INT32 tag)272 size_t FIL_ReadFileTag(char const *name, UINT8 **buffer, INT32 tag)
273 {
274 FILE *handle = NULL;
275 size_t count, length;
276 UINT8 *buf;
277
278 if (FIL_ReadFileOK(name))
279 handle = fopen(name, "rb");
280
281 if (!handle)
282 return 0;
283
284 fseek(handle,0,SEEK_END);
285 length = ftell(handle);
286 fseek(handle,0,SEEK_SET);
287
288 buf = Z_Malloc(length + 1, tag, NULL);
289 count = fread(buf, 1, length, handle);
290 fclose(handle);
291
292 if (count < length)
293 {
294 Z_Free(buf);
295 return 0;
296 }
297
298 // append 0 byte for script text files
299 buf[length] = 0;
300
301 *buffer = buf;
302 return length;
303 }
304
305 /** Makes a copy of a text file with all newlines converted into LF newlines.
306 *
307 * \param textfilename The name of the source file
308 * \param binfilename The name of the destination file
309 */
FIL_ConvertTextFileToBinary(const char * textfilename,const char * binfilename)310 boolean FIL_ConvertTextFileToBinary(const char *textfilename, const char *binfilename)
311 {
312 FILE *textfile;
313 FILE *binfile;
314 UINT8 buffer[1024];
315 size_t count;
316 boolean success;
317
318 textfile = fopen(textfilename, "r");
319 if (!textfile)
320 return false;
321
322 binfile = fopen(binfilename, "wb");
323 if (!binfile)
324 {
325 fclose(textfile);
326 return false;
327 }
328
329 do
330 {
331 count = fread(buffer, 1, sizeof(buffer), textfile);
332 fwrite(buffer, 1, count, binfile);
333 } while (count);
334
335 success = !(ferror(textfile) || ferror(binfile));
336
337 fclose(textfile);
338 fclose(binfile);
339
340 return success;
341 }
342
343 /** Check if the filename exists
344 *
345 * \param name Filename to check.
346 * \return true if file exists, false if it doesn't.
347 */
FIL_FileExists(char const * name)348 boolean FIL_FileExists(char const *name)
349 {
350 return access(name,0)+1; //F_OK
351 }
352
353
354 /** Check if the filename OK to write
355 *
356 * \param name Filename to check.
357 * \return true if file write-able, false if it doesn't.
358 */
FIL_WriteFileOK(char const * name)359 boolean FIL_WriteFileOK(char const *name)
360 {
361 return access(name,2)+1; //W_OK
362 }
363
364
365 /** Check if the filename OK to read
366 *
367 * \param name Filename to check.
368 * \return true if file read-able, false if it doesn't.
369 */
FIL_ReadFileOK(char const * name)370 boolean FIL_ReadFileOK(char const *name)
371 {
372 return access(name,4)+1; //R_OK
373 }
374
375 /** Check if the filename OK to read/write
376 *
377 * \param name Filename to check.
378 * \return true if file (read/write)-able, false if it doesn't.
379 */
FIL_FileOK(char const * name)380 boolean FIL_FileOK(char const *name)
381 {
382 return access(name,6)+1; //R_OK|W_OK
383 }
384
385
386 /** Checks if a pathname has a file extension and adds the extension provided
387 * if not.
388 *
389 * \param path Pathname to check.
390 * \param extension Extension to add if no extension is there.
391 */
FIL_DefaultExtension(char * path,const char * extension)392 void FIL_DefaultExtension(char *path, const char *extension)
393 {
394 char *src;
395
396 // search for '.' from end to begin, add .EXT only when not found
397 src = path + strlen(path) - 1;
398
399 while (*src != '/' && src != path)
400 {
401 if (*src == '.')
402 return; // it has an extension
403 src--;
404 }
405
406 strcat(path, extension);
407 }
408
FIL_ForceExtension(char * path,const char * extension)409 void FIL_ForceExtension(char *path, const char *extension)
410 {
411 char *src;
412
413 // search for '.' from end to begin, add .EXT only when not found
414 src = path + strlen(path) - 1;
415
416 while (*src != '/' && src != path)
417 {
418 if (*src == '.')
419 {
420 *src = '\0';
421 break; // it has an extension
422 }
423 src--;
424 }
425
426 strcat(path, extension);
427 }
428
429 /** Checks if a filename extension is found.
430 * Lump names do not contain dots.
431 *
432 * \param in String to check.
433 * \return True if an extension is found, otherwise false.
434 */
FIL_CheckExtension(const char * in)435 boolean FIL_CheckExtension(const char *in)
436 {
437 while (*in++)
438 if (*in == '.')
439 return true;
440
441 return false;
442 }
443
444 // ==========================================================================
445 // CONFIGURATION FILE
446 // ==========================================================================
447
448 //
449 // DEFAULTS
450 //
451
452 char configfile[MAX_WADPATH];
453
454 // ==========================================================================
455 // CONFIGURATION
456 // ==========================================================================
457 static boolean gameconfig_loaded = false; // true once config.cfg loaded AND executed
458
459 /** Saves a player's config, possibly to a particular file.
460 *
461 * \sa Command_LoadConfig_f
462 */
Command_SaveConfig_f(void)463 void Command_SaveConfig_f(void)
464 {
465 char tmpstr[MAX_WADPATH];
466
467 if (COM_Argc() < 2)
468 {
469 CONS_Printf(M_GetText("saveconfig <filename[.cfg]> [-silent] : save config to a file\n"));
470 return;
471 }
472 strcpy(tmpstr, COM_Argv(1));
473 FIL_ForceExtension(tmpstr, ".cfg");
474
475 M_SaveConfig(tmpstr);
476 if (stricmp(COM_Argv(2), "-silent"))
477 CONS_Printf(M_GetText("config saved as %s\n"), configfile);
478 }
479
480 /** Loads a game config, possibly from a particular file.
481 *
482 * \sa Command_SaveConfig_f, Command_ChangeConfig_f
483 */
Command_LoadConfig_f(void)484 void Command_LoadConfig_f(void)
485 {
486 if (COM_Argc() != 2)
487 {
488 CONS_Printf(M_GetText("loadconfig <filename[.cfg]> : load config from a file\n"));
489 return;
490 }
491
492 strcpy(configfile, COM_Argv(1));
493 FIL_ForceExtension(configfile, ".cfg");
494
495 // load default control
496 G_ClearAllControlKeys();
497 G_CopyControls(gamecontrol, gamecontroldefault[gcs_fps], NULL, 0);
498 G_CopyControls(gamecontrolbis, gamecontrolbisdefault[gcs_fps], NULL, 0);
499
500 // temporarily reset execversion to default
501 CV_ToggleExecVersion(true);
502 COM_BufInsertText(va("%s \"%s\"\n", cv_execversion.name, cv_execversion.defaultvalue));
503 CV_InitFilterVar();
504
505 // exec the config
506 COM_BufInsertText(va("exec \"%s\"\n", configfile));
507
508 // don't filter anymore vars and don't let this convsvar be changed
509 COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION));
510 CV_ToggleExecVersion(false);
511 }
512
513 /** Saves the current configuration and loads another.
514 *
515 * \sa Command_LoadConfig_f, Command_SaveConfig_f
516 */
Command_ChangeConfig_f(void)517 void Command_ChangeConfig_f(void)
518 {
519 if (COM_Argc() != 2)
520 {
521 CONS_Printf(M_GetText("changeconfig <filename[.cfg]> : save current config and load another\n"));
522 return;
523 }
524
525 COM_BufAddText(va("saveconfig \"%s\"\n", configfile));
526 COM_BufAddText(va("loadconfig \"%s\"\n", COM_Argv(1)));
527 }
528
529 /** Loads the default config file.
530 *
531 * \sa Command_LoadConfig_f
532 */
M_FirstLoadConfig(void)533 void M_FirstLoadConfig(void)
534 {
535 // configfile is initialised by d_main when searching for the wad?
536
537 // check for a custom config file
538 if (M_CheckParm("-config") && M_IsNextParm())
539 {
540 strcpy(configfile, M_GetNextParm());
541 CONS_Printf(M_GetText("config file: %s\n"), configfile);
542 }
543
544 // load default control
545 G_DefineDefaultControls();
546 G_CopyControls(gamecontrol, gamecontroldefault[gcs_fps], NULL, 0);
547 G_CopyControls(gamecontrolbis, gamecontrolbisdefault[gcs_fps], NULL, 0);
548
549 // register execversion here before we load any configs
550 CV_RegisterVar(&cv_execversion);
551
552 // temporarily reset execversion to default
553 // we shouldn't need to do this, but JUST in case...
554 CV_ToggleExecVersion(true);
555 COM_BufInsertText(va("%s \"%s\"\n", cv_execversion.name, cv_execversion.defaultvalue));
556 CV_InitFilterVar();
557
558 // load config, make sure those commands doesnt require the screen...
559 COM_BufInsertText(va("exec \"%s\"\n", configfile));
560 // no COM_BufExecute() needed; that does it right away
561
562 // don't filter anymore vars and don't let this convsvar be changed
563 COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION));
564 CV_ToggleExecVersion(false);
565
566 // make sure I_Quit() will write back the correct config
567 // (do not write back the config if it crash before)
568 gameconfig_loaded = true;
569
570 // reset to default player stuff
571 COM_BufAddText (va("%s \"%s\"\n",cv_skin.name,cv_defaultskin.string));
572 COM_BufAddText (va("%s \"%s\"\n",cv_playercolor.name,cv_defaultplayercolor.string));
573 COM_BufAddText (va("%s \"%s\"\n",cv_skin2.name,cv_defaultskin2.string));
574 COM_BufAddText (va("%s \"%s\"\n",cv_playercolor2.name,cv_defaultplayercolor2.string));
575 }
576
577 /** Saves the game configuration.
578 *
579 * \sa Command_SaveConfig_f
580 */
M_SaveConfig(const char * filename)581 void M_SaveConfig(const char *filename)
582 {
583 FILE *f;
584 char *filepath;
585
586 // make sure not to write back the config until it's been correctly loaded
587 if (!gameconfig_loaded)
588 return;
589
590 // can change the file name
591 if (filename)
592 {
593 if (!strstr(filename, ".cfg"))
594 {
595 CONS_Alert(CONS_NOTICE, M_GetText("Config filename must be .cfg\n"));
596 return;
597 }
598
599 // append srb2home to beginning of filename
600 // but check if srb2home isn't already there, first
601 if (!strstr(filename, srb2home))
602 filepath = va(pandf,srb2home, filename);
603 else
604 filepath = Z_StrDup(filename);
605
606 f = fopen(filepath, "w");
607 // change it only if valid
608 if (f)
609 strcpy(configfile, filepath);
610 else
611 {
612 CONS_Alert(CONS_ERROR, M_GetText("Couldn't save game config file %s\n"), filepath);
613 return;
614 }
615 }
616 else
617 {
618 if (!strstr(configfile, ".cfg"))
619 {
620 CONS_Alert(CONS_NOTICE, M_GetText("Config filename must be .cfg\n"));
621 return;
622 }
623
624 f = fopen(configfile, "w");
625 if (!f)
626 {
627 CONS_Alert(CONS_ERROR, M_GetText("Couldn't save game config file %s\n"), configfile);
628 return;
629 }
630 }
631
632 // header message
633 fprintf(f, "// SRB2 configuration file.\n");
634
635 // print execversion FIRST, because subsequent consvars need to be filtered
636 // always print current EXECVERSION
637 fprintf(f, "%s \"%d\"\n", cv_execversion.name, EXECVERSION);
638
639 // FIXME: save key aliases if ever implemented..
640
641 if (tutorialmode && tutorialgcs)
642 {
643 CV_SetValue(&cv_usemouse, tutorialusemouse);
644 CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
645 CV_SetValue(&cv_mousemove, tutorialmousemove);
646 CV_SetValue(&cv_analog[0], tutorialanalog);
647 CV_SaveVariables(f);
648 CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
649 CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
650 CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
651 CV_Set(&cv_analog[0], cv_analog[0].defaultvalue);
652 }
653 else
654 CV_SaveVariables(f);
655
656 if (!dedicated)
657 {
658 if (tutorialmode && tutorialgcs)
659 G_SaveKeySetting(f, gamecontroldefault[gcs_custom], gamecontrolbis); // using gcs_custom as temp storage
660 else
661 G_SaveKeySetting(f, gamecontrol, gamecontrolbis);
662 }
663
664 fclose(f);
665 }
666
667 // ==========================================================================
668 // SCREENSHOTS
669 // ==========================================================================
670 static UINT8 screenshot_palette[768];
M_CreateScreenShotPalette(void)671 static void M_CreateScreenShotPalette(void)
672 {
673 size_t i, j;
674 for (i = 0, j = 0; i < 768; i += 3, j++)
675 {
676 RGBA_t locpal = ((cv_screenshot_colorprofile.value)
677 ? pLocalPalette[(max(st_palette,0)*256)+j]
678 : pMasterPalette[(max(st_palette,0)*256)+j]);
679 screenshot_palette[i] = locpal.s.red;
680 screenshot_palette[i+1] = locpal.s.green;
681 screenshot_palette[i+2] = locpal.s.blue;
682 }
683 }
684
685 #if NUMSCREENS > 2
Newsnapshotfile(const char * pathname,const char * ext)686 static const char *Newsnapshotfile(const char *pathname, const char *ext)
687 {
688 static char freename[13] = "srb2XXXX.ext";
689 int i = 5000; // start in the middle: num screenshots divided by 2
690 int add = i; // how much to add or subtract if wrong; gets divided by 2 each time
691 int result; // -1 = guess too high, 0 = correct, 1 = guess too low
692
693 // find a file name to save it to
694 strcpy(freename+9,ext);
695
696 for (;;)
697 {
698 freename[4] = (char)('0' + (char)(i/1000));
699 freename[5] = (char)('0' + (char)((i/100)%10));
700 freename[6] = (char)('0' + (char)((i/10)%10));
701 freename[7] = (char)('0' + (char)(i%10));
702
703 if (FIL_WriteFileOK(va(pandf,pathname,freename))) // access succeeds
704 result = 1; // too low
705 else // access fails: equal or too high
706 {
707 if (!i)
708 break; // not too high, so it must be equal! YAY!
709
710 freename[4] = (char)('0' + (char)((i-1)/1000));
711 freename[5] = (char)('0' + (char)(((i-1)/100)%10));
712 freename[6] = (char)('0' + (char)(((i-1)/10)%10));
713 freename[7] = (char)('0' + (char)((i-1)%10));
714 if (!FIL_WriteFileOK(va(pandf,pathname,freename))) // access fails
715 result = -1; // too high
716 else
717 break; // not too high, so equal, YAY!
718 }
719
720 add /= 2;
721
722 if (!add) // don't get stuck at 5 due to truncation!
723 add = 1;
724
725 i += add * result;
726
727 if (i < 0 || i > 9999)
728 return NULL;
729 }
730
731 freename[4] = (char)('0' + (char)(i/1000));
732 freename[5] = (char)('0' + (char)((i/100)%10));
733 freename[6] = (char)('0' + (char)((i/10)%10));
734 freename[7] = (char)('0' + (char)(i%10));
735
736 return freename;
737 }
738 #endif
739
740 #ifdef HAVE_PNG
PNG_error(png_structp PNG,png_const_charp pngtext)741 FUNCNORETURN static void PNG_error(png_structp PNG, png_const_charp pngtext)
742 {
743 //CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext);
744 I_Error("libpng error at %p: %s", PNG, pngtext);
745 }
746
PNG_warn(png_structp PNG,png_const_charp pngtext)747 static void PNG_warn(png_structp PNG, png_const_charp pngtext)
748 {
749 CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
750 }
751
M_PNGhdr(png_structp png_ptr,png_infop png_info_ptr,PNG_CONST png_uint_32 width,PNG_CONST png_uint_32 height,PNG_CONST png_byte * palette)752 static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 width, PNG_CONST png_uint_32 height, PNG_CONST png_byte *palette)
753 {
754 const png_byte png_interlace = PNG_INTERLACE_NONE; //PNG_INTERLACE_ADAM7
755 if (palette)
756 {
757 png_colorp png_PLTE = png_malloc(png_ptr, sizeof(png_color)*256); //palette
758 const png_byte *pal = palette;
759 png_uint_16 i;
760 for (i = 0; i < 256; i++)
761 {
762 png_PLTE[i].red = *pal; pal++;
763 png_PLTE[i].green = *pal; pal++;
764 png_PLTE[i].blue = *pal; pal++;
765 }
766 png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE,
767 png_interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
768 png_write_info_before_PLTE(png_ptr, png_info_ptr);
769 png_set_PLTE(png_ptr, png_info_ptr, png_PLTE, 256);
770 png_free(png_ptr, (png_voidp)png_PLTE); // safe in libpng-1.2.1+
771 png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE);
772 png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
773 }
774 else
775 {
776 png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
777 png_interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
778 png_write_info_before_PLTE(png_ptr, png_info_ptr);
779 png_set_compression_strategy(png_ptr, Z_FILTERED);
780 }
781 }
782
M_PNGText(png_structp png_ptr,png_infop png_info_ptr,PNG_CONST png_byte movie)783 static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_byte movie)
784 {
785 #ifdef PNG_TEXT_SUPPORTED
786 #define SRB2PNGTXT 11 //PNG_KEYWORD_MAX_LENGTH(79) is the max
787 png_text png_infotext[SRB2PNGTXT];
788 char keytxt[SRB2PNGTXT][12] = {
789 "Title", "Description", "Playername", "Mapnum", "Mapname",
790 "Location", "Interface", "Render Mode", "Revision", "Build Date", "Build Time"};
791 char titletxt[] = "Sonic Robo Blast 2 " VERSIONSTRING;
792 png_charp playertxt = cv_playername.zstring;
793 char desctxt[] = "SRB2 Screenshot";
794 char Movietxt[] = "SRB2 Movie";
795 size_t i;
796 char interfacetxt[] =
797 #ifdef HAVE_SDL
798 "SDL";
799 #elif defined (_WINDOWS)
800 "DirectX";
801 #else
802 "Unknown";
803 #endif
804 char rendermodetxt[9];
805 char maptext[8];
806 char lvlttltext[48];
807 char locationtxt[40];
808 char ctrevision[40];
809 char ctdate[40];
810 char cttime[40];
811
812 switch (rendermode)
813 {
814 case render_soft:
815 strcpy(rendermodetxt, "Software");
816 break;
817 case render_opengl:
818 strcpy(rendermodetxt, "OpenGL");
819 break;
820 default: // Just in case
821 strcpy(rendermodetxt, "None");
822 break;
823 }
824
825 if (gamestate == GS_LEVEL)
826 snprintf(maptext, 8, "%s", G_BuildMapName(gamemap));
827 else
828 snprintf(maptext, 8, "Unknown");
829
830 if (gamestate == GS_LEVEL && mapheaderinfo[gamemap-1]->lvlttl[0] != '\0')
831 snprintf(lvlttltext, 48, "%s%s%s",
832 mapheaderinfo[gamemap-1]->lvlttl,
833 (mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " Zone",
834 (mapheaderinfo[gamemap-1]->actnum > 0) ? va(" %d",mapheaderinfo[gamemap-1]->actnum) : "");
835 else
836 snprintf(lvlttltext, 48, "Unknown");
837
838 if (gamestate == GS_LEVEL && &players[displayplayer] && players[displayplayer].mo)
839 snprintf(locationtxt, 40, "X:%d Y:%d Z:%d A:%d",
840 players[displayplayer].mo->x>>FRACBITS,
841 players[displayplayer].mo->y>>FRACBITS,
842 players[displayplayer].mo->z>>FRACBITS,
843 FixedInt(AngleFixed(players[displayplayer].mo->angle)));
844 else
845 snprintf(locationtxt, 40, "Unknown");
846
847 memset(png_infotext,0x00,sizeof (png_infotext));
848
849 for (i = 0; i < SRB2PNGTXT; i++)
850 png_infotext[i].key = keytxt[i];
851
852 png_infotext[0].text = titletxt;
853 if (movie)
854 png_infotext[1].text = Movietxt;
855 else
856 png_infotext[1].text = desctxt;
857 png_infotext[2].text = playertxt;
858 png_infotext[3].text = maptext;
859 png_infotext[4].text = lvlttltext;
860 png_infotext[5].text = locationtxt;
861 png_infotext[6].text = interfacetxt;
862 png_infotext[7].text = rendermodetxt;
863 png_infotext[8].text = strncpy(ctrevision, comprevision, sizeof(ctrevision)-1);
864 png_infotext[9].text = strncpy(ctdate, compdate, sizeof(ctdate)-1);
865 png_infotext[10].text = strncpy(cttime, comptime, sizeof(cttime)-1);
866
867 png_set_text(png_ptr, png_info_ptr, png_infotext, SRB2PNGTXT);
868 #undef SRB2PNGTXT
869 #endif
870 }
871
M_PNGImage(png_structp png_ptr,png_infop png_info_ptr,PNG_CONST png_uint_32 height,png_bytep png_buf)872 static inline void M_PNGImage(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 height, png_bytep png_buf)
873 {
874 png_uint_32 pitch = png_get_rowbytes(png_ptr, png_info_ptr);
875 png_bytepp row_pointers = png_malloc(png_ptr, height* sizeof (png_bytep));
876 png_uint_32 y;
877 for (y = 0; y < height; y++)
878 {
879 row_pointers[y] = png_buf;
880 png_buf += pitch;
881 }
882 png_write_image(png_ptr, row_pointers);
883 png_free(png_ptr, (png_voidp)row_pointers);
884 }
885
886 #ifdef USE_APNG
887 static png_structp apng_ptr = NULL;
888 static png_infop apng_info_ptr = NULL;
889 static apng_infop apng_ainfo_ptr = NULL;
890 static png_FILE_p apng_FILE = NULL;
891 static png_uint_32 apng_frames = 0;
892 #ifdef PNG_STATIC // Win32 build have static libpng
893 #define aPNG_set_acTL png_set_acTL
894 #define aPNG_write_frame_head png_write_frame_head
895 #define aPNG_write_frame_tail png_write_frame_tail
896 #else // outside libpng may not have apng support
897
898 #ifndef PNG_WRITE_APNG_SUPPORTED // libpng header may not have apng patch
899
900 #ifndef PNG_INFO_acTL
901 #define PNG_INFO_acTL 0x10000L
902 #endif
903 #ifndef PNG_INFO_fcTL
904 #define PNG_INFO_fcTL 0x20000L
905 #endif
906 #ifndef PNG_FIRST_FRAME_HIDDEN
907 #define PNG_FIRST_FRAME_HIDDEN 0x0001
908 #endif
909 #ifndef PNG_DISPOSE_OP_NONE
910 #define PNG_DISPOSE_OP_NONE 0x00
911 #endif
912 #ifndef PNG_DISPOSE_OP_BACKGROUND
913 #define PNG_DISPOSE_OP_BACKGROUND 0x01
914 #endif
915 #ifndef PNG_DISPOSE_OP_PREVIOUS
916 #define PNG_DISPOSE_OP_PREVIOUS 0x02
917 #endif
918 #ifndef PNG_BLEND_OP_SOURCE
919 #define PNG_BLEND_OP_SOURCE 0x00
920 #endif
921 #ifndef PNG_BLEND_OP_OVER
922 #define PNG_BLEND_OP_OVER 0x01
923 #endif
924 #ifndef PNG_HAVE_acTL
925 #define PNG_HAVE_acTL 0x4000
926 #endif
927 #ifndef PNG_HAVE_fcTL
928 #define PNG_HAVE_fcTL 0x8000L
929 #endif
930
931 #endif
932 typedef png_uint_32 (*P_png_set_acTL) (png_structp png_ptr,
933 png_infop info_ptr, png_uint_32 num_frames, png_uint_32 num_plays);
934 typedef void (*P_png_write_frame_head) (png_structp png_ptr,
935 png_infop info_ptr, png_bytepp row_pointers,
936 png_uint_32 width, png_uint_32 height,
937 png_uint_32 x_offset, png_uint_32 y_offset,
938 png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op,
939 png_byte blend_op);
940
941 typedef void (*P_png_write_frame_tail) (png_structp png_ptr,
942 png_infop info_ptr);
943 static P_png_set_acTL aPNG_set_acTL = NULL;
944 static P_png_write_frame_head aPNG_write_frame_head = NULL;
945 static P_png_write_frame_tail aPNG_write_frame_tail = NULL;
946 #endif
947
M_PNGLib(void)948 static inline boolean M_PNGLib(void)
949 {
950 #ifdef PNG_STATIC // Win32 build have static libpng
951 return true;
952 #else
953 static void *pnglib = NULL;
954 if (aPNG_set_acTL && aPNG_write_frame_head && aPNG_write_frame_tail)
955 return true;
956 if (pnglib)
957 return false;
958 #ifdef _WIN32
959 pnglib = GetModuleHandleA("libpng.dll");
960 if (!pnglib)
961 pnglib = GetModuleHandleA("libpng12.dll");
962 if (!pnglib)
963 pnglib = GetModuleHandleA("libpng13.dll");
964 #elif defined (HAVE_SDL)
965 #ifdef __APPLE__
966 pnglib = hwOpen("libpng.dylib");
967 #else
968 pnglib = hwOpen("libpng.so");
969 #endif
970 #endif
971 if (!pnglib)
972 return false;
973 #ifdef HAVE_SDL
974 aPNG_set_acTL = hwSym("png_set_acTL", pnglib);
975 aPNG_write_frame_head = hwSym("png_write_frame_head", pnglib);
976 aPNG_write_frame_tail = hwSym("png_write_frame_tail", pnglib);
977 #endif
978 #ifdef _WIN32
979 aPNG_set_acTL = GetProcAddress("png_set_acTL", pnglib);
980 aPNG_write_frame_head = GetProcAddress("png_write_frame_head", pnglib);
981 aPNG_write_frame_tail = GetProcAddress("png_write_frame_tail", pnglib);
982 #endif
983 return (aPNG_set_acTL && aPNG_write_frame_head && aPNG_write_frame_tail);
984 #endif
985 }
986
M_PNGFrame(png_structp png_ptr,png_infop png_info_ptr,png_bytep png_buf)987 static void M_PNGFrame(png_structp png_ptr, png_infop png_info_ptr, png_bytep png_buf)
988 {
989 png_uint_16 downscale = apng_downscale ? vid.dupx : 1;
990
991 png_uint_32 pitch = png_get_rowbytes(png_ptr, png_info_ptr);
992 PNG_CONST png_uint_32 width = vid.width / downscale;
993 PNG_CONST png_uint_32 height = vid.height / downscale;
994 png_bytepp row_pointers = png_malloc(png_ptr, height * sizeof (png_bytep));
995 png_uint_32 x, y;
996 png_uint_16 framedelay = (png_uint_16)cv_apng_delay.value;
997
998 apng_frames++;
999
1000 for (y = 0; y < height; y++)
1001 {
1002 row_pointers[y] = malloc(pitch * sizeof(png_byte));
1003 for (x = 0; x < width; x++)
1004 row_pointers[y][x] = png_buf[x * downscale];
1005 png_buf += pitch * (downscale * downscale);
1006 }
1007 //for (x = 0; x < width; x++)
1008 //{
1009 // printf("%d", x);
1010 // row_pointers[y][x] = 0;
1011 //}
1012 /* row_pointers[y] = calloc(1, sizeof(png_bytep));
1013 png_buf += pitch * 2;
1014 }*/
1015
1016 #ifndef PNG_STATIC
1017 if (aPNG_write_frame_head)
1018 #endif
1019 aPNG_write_frame_head(apng_ptr, apng_info_ptr, row_pointers,
1020 width, /* width */
1021 height, /* height */
1022 0, /* x offset */
1023 0, /* y offset */
1024 framedelay, TICRATE,/* delay numerator and denominator */
1025 PNG_DISPOSE_OP_BACKGROUND, /* dispose */
1026 PNG_BLEND_OP_SOURCE /* blend */
1027 );
1028
1029 png_write_image(png_ptr, row_pointers);
1030
1031 #ifndef PNG_STATIC
1032 if (aPNG_write_frame_tail)
1033 #endif
1034 aPNG_write_frame_tail(apng_ptr, apng_info_ptr);
1035
1036 png_free(png_ptr, (png_voidp)row_pointers);
1037 }
1038
M_PNGfix_acTL(png_structp png_ptr,png_infop png_info_ptr,apng_infop png_ainfo_ptr)1039 static void M_PNGfix_acTL(png_structp png_ptr, png_infop png_info_ptr,
1040 apng_infop png_ainfo_ptr)
1041 {
1042 apng_set_acTL(png_ptr, png_info_ptr, png_ainfo_ptr, apng_frames, 0);
1043
1044 #ifndef NO_PNG_DEBUG
1045 png_debug(1, "in png_write_acTL\n");
1046 #endif
1047 }
1048
M_SetupaPNG(png_const_charp filename,png_bytep pal)1049 static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
1050 {
1051 png_uint_16 downscale;
1052
1053 apng_downscale = (!!cv_apng_downscale.value);
1054
1055 downscale = apng_downscale ? vid.dupx : 1;
1056
1057 apng_FILE = fopen(filename,"wb+"); // + mode for reading
1058 if (!apng_FILE)
1059 {
1060 CONS_Debug(DBG_RENDER, "M_StartMovie: Error on opening %s for write\n", filename);
1061 return false;
1062 }
1063
1064 apng_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
1065 PNG_error, PNG_warn);
1066 if (!apng_ptr)
1067 {
1068 CONS_Debug(DBG_RENDER, "M_StartMovie: Error on initialize libpng\n");
1069 fclose(apng_FILE);
1070 remove(filename);
1071 return false;
1072 }
1073
1074 apng_info_ptr = png_create_info_struct(apng_ptr);
1075 if (!apng_info_ptr)
1076 {
1077 CONS_Debug(DBG_RENDER, "M_StartMovie: Error on allocate for libpng\n");
1078 png_destroy_write_struct(&apng_ptr, NULL);
1079 fclose(apng_FILE);
1080 remove(filename);
1081 return false;
1082 }
1083
1084 apng_ainfo_ptr = apng_create_info_struct(apng_ptr);
1085 if (!apng_ainfo_ptr)
1086 {
1087 CONS_Debug(DBG_RENDER, "M_StartMovie: Error on allocate for apng\n");
1088 png_destroy_write_struct(&apng_ptr, &apng_info_ptr);
1089 fclose(apng_FILE);
1090 remove(filename);
1091 return false;
1092 }
1093
1094 png_init_io(apng_ptr, apng_FILE);
1095
1096 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
1097 png_set_user_limits(apng_ptr, MAXVIDWIDTH, MAXVIDHEIGHT);
1098 #endif
1099
1100 //png_set_filter(apng_ptr, 0, PNG_ALL_FILTERS);
1101
1102 png_set_compression_level(apng_ptr, cv_zlib_levela.value);
1103 png_set_compression_mem_level(apng_ptr, cv_zlib_memorya.value);
1104 png_set_compression_strategy(apng_ptr, cv_zlib_strategya.value);
1105 png_set_compression_window_bits(apng_ptr, cv_zlib_window_bitsa.value);
1106
1107 M_PNGhdr(apng_ptr, apng_info_ptr, vid.width / downscale, vid.height / downscale, pal);
1108
1109 M_PNGText(apng_ptr, apng_info_ptr, true);
1110
1111 apng_set_set_acTL_fn(apng_ptr, apng_ainfo_ptr, aPNG_set_acTL);
1112
1113 apng_set_acTL(apng_ptr, apng_info_ptr, apng_ainfo_ptr, PNG_UINT_31_MAX, 0);
1114
1115 apng_write_info(apng_ptr, apng_info_ptr, apng_ainfo_ptr);
1116
1117 apng_frames = 0;
1118
1119 return true;
1120 }
1121 #endif
1122 #endif
1123
1124 // ==========================================================================
1125 // MOVIE MODE
1126 // ==========================================================================
1127 #if NUMSCREENS > 2
M_StartMovieAPNG(const char * pathname)1128 static inline moviemode_t M_StartMovieAPNG(const char *pathname)
1129 {
1130 #ifdef USE_APNG
1131 UINT8 *palette = NULL;
1132 const char *freename = NULL;
1133 boolean ret = false;
1134
1135 if (!M_PNGLib())
1136 {
1137 CONS_Alert(CONS_ERROR, "Couldn't create aPNG: libpng not found\n");
1138 return MM_OFF;
1139 }
1140
1141 if (!(freename = Newsnapshotfile(pathname,"png")))
1142 {
1143 CONS_Alert(CONS_ERROR, "Couldn't create aPNG: no slots open in %s\n", pathname);
1144 return MM_OFF;
1145 }
1146
1147 if (rendermode == render_soft)
1148 {
1149 M_CreateScreenShotPalette();
1150 palette = screenshot_palette;
1151 }
1152
1153 ret = M_SetupaPNG(va(pandf,pathname,freename), palette);
1154
1155 if (!ret)
1156 {
1157 CONS_Alert(CONS_ERROR, "Couldn't create aPNG: error creating %s in %s\n", freename, pathname);
1158 return MM_OFF;
1159 }
1160 return MM_APNG;
1161 #else
1162 // no APNG support exists
1163 (void)pathname;
1164 CONS_Alert(CONS_ERROR, "Couldn't create aPNG: this build lacks aPNG support\n");
1165 return MM_OFF;
1166 #endif
1167 }
1168
M_StartMovieGIF(const char * pathname)1169 static inline moviemode_t M_StartMovieGIF(const char *pathname)
1170 {
1171 #ifdef HAVE_ANIGIF
1172 const char *freename;
1173
1174 if (!(freename = Newsnapshotfile(pathname,"gif")))
1175 {
1176 CONS_Alert(CONS_ERROR, "Couldn't create GIF: no slots open in %s\n", pathname);
1177 return MM_OFF;
1178 }
1179
1180 if (!GIF_open(va(pandf,pathname,freename)))
1181 {
1182 CONS_Alert(CONS_ERROR, "Couldn't create GIF: error creating %s in %s\n", freename, pathname);
1183 return MM_OFF;
1184 }
1185 return MM_GIF;
1186 #else
1187 // no GIF support exists
1188 (void)pathname;
1189 CONS_Alert(CONS_ERROR, "Couldn't create GIF: this build lacks GIF support\n");
1190 return MM_OFF;
1191 #endif
1192 }
1193 #endif
1194
M_StartMovie(void)1195 void M_StartMovie(void)
1196 {
1197 #if NUMSCREENS > 2
1198 char pathname[MAX_WADPATH];
1199
1200 if (moviemode)
1201 return;
1202
1203 if (cv_movie_option.value == 0)
1204 strcpy(pathname, usehome ? srb2home : srb2path);
1205 else if (cv_movie_option.value == 1)
1206 strcpy(pathname, srb2home);
1207 else if (cv_movie_option.value == 2)
1208 strcpy(pathname, srb2path);
1209 else if (cv_movie_option.value == 3 && *cv_movie_folder.string != '\0')
1210 strcpy(pathname, cv_movie_folder.string);
1211
1212 if (cv_movie_option.value != 3)
1213 {
1214 strcat(pathname, PATHSEP"movies"PATHSEP);
1215 I_mkdir(pathname, 0755);
1216 }
1217
1218 if (rendermode == render_none)
1219 I_Error("Can't make a movie without a render system\n");
1220
1221 switch (cv_moviemode.value)
1222 {
1223 case MM_GIF:
1224 moviemode = M_StartMovieGIF(pathname);
1225 break;
1226 case MM_APNG:
1227 moviemode = M_StartMovieAPNG(pathname);
1228 break;
1229 case MM_SCREENSHOT:
1230 moviemode = MM_SCREENSHOT;
1231 break;
1232 default: //???
1233 return;
1234 }
1235
1236 if (moviemode == MM_APNG)
1237 CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "aPNG");
1238 else if (moviemode == MM_GIF)
1239 CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "GIF");
1240 else if (moviemode == MM_SCREENSHOT)
1241 CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "screenshots");
1242
1243 //singletics = (moviemode != MM_OFF);
1244 #endif
1245 }
1246
M_SaveFrame(void)1247 void M_SaveFrame(void)
1248 {
1249 #if NUMSCREENS > 2
1250 // paranoia: should be unnecessary without singletics
1251 static tic_t oldtic = 0;
1252
1253 if (oldtic == I_GetTime())
1254 return;
1255 else
1256 oldtic = I_GetTime();
1257
1258 switch (moviemode)
1259 {
1260 case MM_SCREENSHOT:
1261 takescreenshot = true;
1262 return;
1263 case MM_GIF:
1264 GIF_frame();
1265 return;
1266 case MM_APNG:
1267 #ifdef USE_APNG
1268 {
1269 UINT8 *linear = NULL;
1270 if (!apng_FILE) // should not happen!!
1271 {
1272 moviemode = MM_OFF;
1273 return;
1274 }
1275
1276 if (rendermode == render_soft)
1277 {
1278 // munge planar buffer to linear
1279 linear = screens[2];
1280 I_ReadScreen(linear);
1281 }
1282 #ifdef HWRENDER
1283 else
1284 linear = HWR_GetScreenshot();
1285 #endif
1286 M_PNGFrame(apng_ptr, apng_info_ptr, (png_bytep)linear);
1287 #ifdef HWRENDER
1288 if (rendermode != render_soft && linear)
1289 free(linear);
1290 #endif
1291
1292 if (apng_frames == PNG_UINT_31_MAX)
1293 {
1294 CONS_Alert(CONS_NOTICE, M_GetText("Max movie size reached\n"));
1295 M_StopMovie();
1296 }
1297 }
1298 #else
1299 moviemode = MM_OFF;
1300 #endif
1301 return;
1302 default:
1303 return;
1304 }
1305 #endif
1306 }
1307
M_StopMovie(void)1308 void M_StopMovie(void)
1309 {
1310 #if NUMSCREENS > 2
1311 switch (moviemode)
1312 {
1313 case MM_GIF:
1314 if (!GIF_close())
1315 return;
1316 break;
1317 case MM_APNG:
1318 #ifdef USE_APNG
1319 if (!apng_FILE)
1320 return;
1321
1322 if (apng_frames)
1323 {
1324 M_PNGfix_acTL(apng_ptr, apng_info_ptr, apng_ainfo_ptr);
1325 apng_write_end(apng_ptr, apng_info_ptr, apng_ainfo_ptr);
1326 }
1327
1328 png_destroy_write_struct(&apng_ptr, &apng_info_ptr);
1329
1330 fclose(apng_FILE);
1331 apng_FILE = NULL;
1332 CONS_Printf("aPNG closed; wrote %u frames\n", (UINT32)apng_frames);
1333 apng_frames = 0;
1334 break;
1335 #else
1336 return;
1337 #endif
1338 case MM_SCREENSHOT:
1339 break;
1340 default:
1341 return;
1342 }
1343 moviemode = MM_OFF;
1344 CONS_Printf(M_GetText("Movie mode disabled.\n"));
1345 #endif
1346 }
1347
1348 // ==========================================================================
1349 // SCREEN SHOTS
1350 // ==========================================================================
1351 #ifdef USE_PNG
1352 /** Writes a PNG file to disk.
1353 *
1354 * \param filename Filename to write to.
1355 * \param data The image data.
1356 * \param width Width of the picture.
1357 * \param height Height of the picture.
1358 * \param palette Palette of image data.
1359 * \note if palette is NULL, BGR888 format
1360 */
M_SavePNG(const char * filename,void * data,int width,int height,const UINT8 * palette)1361 boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette)
1362 {
1363 png_structp png_ptr;
1364 png_infop png_info_ptr;
1365 PNG_CONST png_byte *PLTE = (const png_byte *)palette;
1366 #ifdef PNG_SETJMP_SUPPORTED
1367 #ifdef USE_FAR_KEYWORD
1368 jmp_buf jmpbuf;
1369 #endif
1370 #endif
1371 png_FILE_p png_FILE;
1372
1373 png_FILE = fopen(filename,"wb");
1374 if (!png_FILE)
1375 {
1376 CONS_Debug(DBG_RENDER, "M_SavePNG: Error on opening %s for write\n", filename);
1377 return false;
1378 }
1379
1380 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
1381 if (!png_ptr)
1382 {
1383 CONS_Debug(DBG_RENDER, "M_SavePNG: Error on initialize libpng\n");
1384 fclose(png_FILE);
1385 remove(filename);
1386 return false;
1387 }
1388
1389 png_info_ptr = png_create_info_struct(png_ptr);
1390 if (!png_info_ptr)
1391 {
1392 CONS_Debug(DBG_RENDER, "M_SavePNG: Error on allocate for libpng\n");
1393 png_destroy_write_struct(&png_ptr, NULL);
1394 fclose(png_FILE);
1395 remove(filename);
1396 return false;
1397 }
1398
1399 #ifdef USE_FAR_KEYWORD
1400 if (setjmp(jmpbuf))
1401 #else
1402 if (setjmp(png_jmpbuf(png_ptr)))
1403 #endif
1404 {
1405 //CONS_Debug(DBG_RENDER, "libpng write error on %s\n", filename);
1406 png_destroy_write_struct(&png_ptr, &png_info_ptr);
1407 fclose(png_FILE);
1408 remove(filename);
1409 return false;
1410 }
1411 #ifdef USE_FAR_KEYWORD
1412 png_memcpy(png_jmpbuf(png_ptr),jmpbuf, sizeof (jmp_buf));
1413 #endif
1414 png_init_io(png_ptr, png_FILE);
1415
1416 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
1417 png_set_user_limits(png_ptr, MAXVIDWIDTH, MAXVIDHEIGHT);
1418 #endif
1419
1420 //png_set_filter(png_ptr, 0, PNG_ALL_FILTERS);
1421
1422 png_set_compression_level(png_ptr, cv_zlib_level.value);
1423 png_set_compression_mem_level(png_ptr, cv_zlib_memory.value);
1424 png_set_compression_strategy(png_ptr, cv_zlib_strategy.value);
1425 png_set_compression_window_bits(png_ptr, cv_zlib_window_bits.value);
1426
1427 M_PNGhdr(png_ptr, png_info_ptr, width, height, PLTE);
1428
1429 M_PNGText(png_ptr, png_info_ptr, false);
1430
1431 png_write_info(png_ptr, png_info_ptr);
1432
1433 M_PNGImage(png_ptr, png_info_ptr, height, data);
1434
1435 png_write_end(png_ptr, png_info_ptr);
1436 png_destroy_write_struct(&png_ptr, &png_info_ptr);
1437
1438 fclose(png_FILE);
1439 return true;
1440 }
1441 #else
1442 /** PCX file structure.
1443 */
1444 typedef struct
1445 {
1446 UINT8 manufacturer;
1447 UINT8 version;
1448 UINT8 encoding;
1449 UINT8 bits_per_pixel;
1450
1451 UINT16 xmin, ymin;
1452 UINT16 xmax, ymax;
1453 UINT16 hres, vres;
1454 UINT8 palette[48];
1455
1456 UINT8 reserved;
1457 UINT8 color_planes;
1458 UINT16 bytes_per_line;
1459 UINT16 palette_type;
1460
1461 char filler[58];
1462 UINT8 data; ///< Unbounded; used for all picture data.
1463 } pcx_t;
1464
1465 /** Writes a PCX file to disk.
1466 *
1467 * \param filename Filename to write to.
1468 * \param data The image data.
1469 * \param width Width of the picture.
1470 * \param height Height of the picture.
1471 * \param palette Palette of image data
1472 */
1473 #if NUMSCREENS > 2
WritePCXfile(const char * filename,const UINT8 * data,int width,int height,const UINT8 * pal)1474 static boolean WritePCXfile(const char *filename, const UINT8 *data, int width, int height, const UINT8 *pal)
1475 {
1476 int i;
1477 size_t length;
1478 pcx_t *pcx;
1479 UINT8 *pack;
1480
1481 pcx = Z_Malloc(width*height*2 + 1000, PU_STATIC, NULL);
1482
1483 pcx->manufacturer = 0x0a; // PCX id
1484 pcx->version = 5; // 256 color
1485 pcx->encoding = 1; // uncompressed
1486 pcx->bits_per_pixel = 8; // 256 color
1487 pcx->xmin = pcx->ymin = 0;
1488 pcx->xmax = SHORT(width - 1);
1489 pcx->ymax = SHORT(height - 1);
1490 pcx->hres = SHORT(width);
1491 pcx->vres = SHORT(height);
1492 memset(pcx->palette, 0, sizeof (pcx->palette));
1493 pcx->reserved = 0;
1494 pcx->color_planes = 1; // chunky image
1495 pcx->bytes_per_line = SHORT(width);
1496 pcx->palette_type = SHORT(1); // not a grey scale
1497 memset(pcx->filler, 0, sizeof (pcx->filler));
1498
1499 // pack the image
1500 pack = &pcx->data;
1501
1502 for (i = 0; i < width*height; i++)
1503 {
1504 if ((*data & 0xc0) != 0xc0)
1505 *pack++ = *data++;
1506 else
1507 {
1508 *pack++ = 0xc1;
1509 *pack++ = *data++;
1510 }
1511 }
1512
1513 // write the palette
1514 *pack++ = 0x0c; // palette ID byte
1515
1516 // write color table
1517 {
1518 for (i = 0; i < 256; i++)
1519 {
1520 *pack++ = *pal; pal++;
1521 *pack++ = *pal; pal++;
1522 *pack++ = *pal; pal++;
1523 }
1524 }
1525
1526 // write output file
1527 length = pack - (UINT8 *)pcx;
1528 i = FIL_WriteFile(filename, pcx, length);
1529
1530 Z_Free(pcx);
1531 return i;
1532 }
1533 #endif
1534 #endif
1535
M_ScreenShot(void)1536 void M_ScreenShot(void)
1537 {
1538 takescreenshot = true;
1539 }
1540
1541 /** Takes a screenshot.
1542 * The screenshot is saved as "srb2xxxx.png" where xxxx is the lowest
1543 * four-digit number for which a file does not already exist.
1544 *
1545 * \sa HWR_ScreenShot
1546 */
M_DoScreenShot(void)1547 void M_DoScreenShot(void)
1548 {
1549 #if NUMSCREENS > 2
1550 const char *freename = NULL;
1551 char pathname[MAX_WADPATH];
1552 boolean ret = false;
1553 UINT8 *linear = NULL;
1554
1555 // Don't take multiple screenshots, obviously
1556 takescreenshot = false;
1557
1558 // how does one take a screenshot without a render system?
1559 if (rendermode == render_none)
1560 return;
1561
1562 if (cv_screenshot_option.value == 0)
1563 strcpy(pathname, usehome ? srb2home : srb2path);
1564 else if (cv_screenshot_option.value == 1)
1565 strcpy(pathname, srb2home);
1566 else if (cv_screenshot_option.value == 2)
1567 strcpy(pathname, srb2path);
1568 else if (cv_screenshot_option.value == 3 && *cv_screenshot_folder.string != '\0')
1569 strcpy(pathname, cv_screenshot_folder.string);
1570
1571 if (cv_screenshot_option.value != 3)
1572 {
1573 strcat(pathname, PATHSEP"screenshots"PATHSEP);
1574 I_mkdir(pathname, 0755);
1575 }
1576
1577 #ifdef USE_PNG
1578 freename = Newsnapshotfile(pathname,"png");
1579 #else
1580 if (rendermode == render_soft)
1581 freename = Newsnapshotfile(pathname,"pcx");
1582 else if (rendermode == render_opengl)
1583 freename = Newsnapshotfile(pathname,"tga");
1584 #endif
1585
1586 if (rendermode == render_soft)
1587 {
1588 // munge planar buffer to linear
1589 linear = screens[2];
1590 I_ReadScreen(linear);
1591 }
1592
1593 if (!freename)
1594 goto failure;
1595
1596 // save the pcx file
1597 #ifdef HWRENDER
1598 if (rendermode == render_opengl)
1599 ret = HWR_Screenshot(va(pandf,pathname,freename));
1600 else
1601 #endif
1602 {
1603 M_CreateScreenShotPalette();
1604 #ifdef USE_PNG
1605 ret = M_SavePNG(va(pandf,pathname,freename), linear, vid.width, vid.height, screenshot_palette);
1606 #else
1607 ret = WritePCXfile(va(pandf,pathname,freename), linear, vid.width, vid.height, screenshot_palette);
1608 #endif
1609 }
1610
1611 failure:
1612 if (ret)
1613 {
1614 if (moviemode != MM_SCREENSHOT)
1615 CONS_Printf(M_GetText("Screen shot %s saved in %s\n"), freename, pathname);
1616 }
1617 else
1618 {
1619 if (freename)
1620 CONS_Alert(CONS_ERROR, M_GetText("Couldn't create screen shot %s in %s\n"), freename, pathname);
1621 else
1622 CONS_Alert(CONS_ERROR, M_GetText("Couldn't create screen shot in %s (all 10000 slots used!)\n"), pathname);
1623
1624 if (moviemode == MM_SCREENSHOT)
1625 M_StopMovie();
1626 }
1627 #endif
1628 }
1629
M_ScreenshotResponder(event_t * ev)1630 boolean M_ScreenshotResponder(event_t *ev)
1631 {
1632 INT32 ch = -1;
1633 if (dedicated || ev->type != ev_keydown)
1634 return false;
1635
1636 ch = ev->data1;
1637
1638 if (ch >= KEY_MOUSE1 && menuactive) // If it's not a keyboard key, then don't allow it in the menus!
1639 return false;
1640
1641 if (ch == KEY_F8 || ch == gamecontrol[gc_screenshot][0] || ch == gamecontrol[gc_screenshot][1]) // remappable F8
1642 M_ScreenShot();
1643 else if (ch == KEY_F9 || ch == gamecontrol[gc_recordgif][0] || ch == gamecontrol[gc_recordgif][1]) // remappable F9
1644 ((moviemode) ? M_StopMovie : M_StartMovie)();
1645 else
1646 return false;
1647 return true;
1648 }
1649
1650 // ==========================================================================
1651 // TRANSLATION FUNCTIONS
1652 // ==========================================================================
1653
1654 // M_StartupLocale.
1655 // Sets up gettext to translate SRB2's strings.
1656 #ifdef GETTEXT
1657 #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
1658 #define GETTEXTDOMAIN1 "/usr/share/locale"
1659 #define GETTEXTDOMAIN2 "/usr/local/share/locale"
1660 #elif defined (_WIN32)
1661 #define GETTEXTDOMAIN1 "."
1662 #endif
1663 #endif // GETTEXT
1664
M_StartupLocale(void)1665 void M_StartupLocale(void)
1666 {
1667 #ifdef GETTEXT
1668 char *textdomhandle = NULL;
1669 #endif //GETTEXT
1670
1671 CONS_Printf("M_StartupLocale...\n");
1672
1673 setlocale(LC_ALL, "");
1674
1675 // Do not set numeric locale as that affects atof
1676 setlocale(LC_NUMERIC, "C");
1677
1678 #ifdef GETTEXT
1679 // FIXME: global name define anywhere?
1680 #ifdef GETTEXTDOMAIN1
1681 textdomhandle = bindtextdomain("srb2", GETTEXTDOMAIN1);
1682 #endif
1683 #ifdef GETTEXTDOMAIN2
1684 if (!textdomhandle)
1685 textdomhandle = bindtextdomain("srb2", GETTEXTDOMAIN2);
1686 #endif
1687 #ifdef GETTEXTDOMAIN3
1688 if (!textdomhandle)
1689 textdomhandle = bindtextdomain("srb2", GETTEXTDOMAIN3);
1690 #endif
1691 #ifdef GETTEXTDOMAIN4
1692 if (!textdomhandle)
1693 textdomhandle = bindtextdomain("srb2", GETTEXTDOMAIN4);
1694 #endif
1695 if (textdomhandle)
1696 textdomain("srb2");
1697 else
1698 CONS_Printf("Could not find locale text domain!\n");
1699 #endif //GETTEXT
1700 }
1701
1702 // ==========================================================================
1703 // MISC STRING FUNCTIONS
1704 // ==========================================================================
1705
1706 /** Returns a temporary string made out of varargs.
1707 * For use with CONS_Printf().
1708 *
1709 * \param format Format string.
1710 * \return Pointer to a static buffer of 1024 characters, containing the
1711 * resulting string.
1712 */
va(const char * format,...)1713 char *va(const char *format, ...)
1714 {
1715 va_list argptr;
1716 static char string[1024];
1717
1718 va_start(argptr, format);
1719 vsprintf(string, format, argptr);
1720 va_end(argptr);
1721
1722 return string;
1723 }
1724
1725 /** Creates a string in the first argument that is the second argument followed
1726 * by the third argument followed by the first argument.
1727 * Useful for making filenames with full path. s1 = s2+s3+s1
1728 *
1729 * \param s1 First string, suffix, and destination.
1730 * \param s2 Second string. Ends up first in the result.
1731 * \param s3 Third string. Ends up second in the result.
1732 */
strcatbf(char * s1,const char * s2,const char * s3)1733 void strcatbf(char *s1, const char *s2, const char *s3)
1734 {
1735 char tmp[1024];
1736
1737 strcpy(tmp, s1);
1738 strcpy(s1, s2);
1739 strcat(s1, s3);
1740 strcat(s1, tmp);
1741 }
1742
1743 /** Converts an ASCII Hex string into an integer. Thanks, Borland!
1744 * <Inuyasha> I don't know if this belongs here specifically, but it sure
1745 * doesn't belong in p_spec.c, that's for sure
1746 *
1747 * \param hexStg Hexadecimal string.
1748 * \return an Integer based off the contents of the string.
1749 */
axtoi(const char * hexStg)1750 INT32 axtoi(const char *hexStg)
1751 {
1752 INT32 n = 0;
1753 INT32 m = 0;
1754 INT32 count;
1755 INT32 intValue = 0;
1756 INT32 digit[8];
1757 while (n < 8)
1758 {
1759 if (hexStg[n] == '\0')
1760 break;
1761 if (hexStg[n] >= '0' && hexStg[n] <= '9') // 0-9
1762 digit[n] = (hexStg[n] & 0x0f);
1763 else if (hexStg[n] >= 'a' && hexStg[n] <= 'f') // a-f
1764 digit[n] = (hexStg[n] & 0x0f) + 9;
1765 else if (hexStg[n] >= 'A' && hexStg[n] <= 'F') // A-F
1766 digit[n] = (hexStg[n] & 0x0f) + 9;
1767 else
1768 break;
1769 n++;
1770 }
1771 count = n;
1772 m = n - 1;
1773 n = 0;
1774 while (n < count)
1775 {
1776 intValue = intValue | (digit[n] << (m << 2));
1777 m--;
1778 n++;
1779 }
1780 return intValue;
1781 }
1782
1783 // Token parser variables
1784
1785 static UINT32 oldendPos = 0; // old value of endPos, used by M_UnGetToken
1786 static UINT32 endPos = 0; // now external to M_GetToken, but still static
1787
1788 /** Token parser for TEXTURES, ANIMDEFS, and potentially other lumps later down the line.
1789 * Was originally R_GetTexturesToken when I was coding up the TEXTURES parser, until I realized I needed it for ANIMDEFS too.
1790 * Parses up to the next whitespace character or comma. When finding the start of the next token, whitespace is skipped.
1791 * Commas are not; if a comma is encountered, then THAT'S returned as the token.
1792 * -Shadow Hog
1793 *
1794 * \param inputString The string to be parsed. If NULL is supplied instead of a string, it will continue parsing the last supplied one.
1795 * The pointer to the last string supplied is stored as a static variable, so be careful not to free it while this function is still using it!
1796 * \return A pointer to a string, containing the fetched token. This is in freshly allocated memory, so be sure to Z_Free() it as appropriate.
1797 */
M_GetToken(const char * inputString)1798 char *M_GetToken(const char *inputString)
1799 {
1800 static const char *stringToUse = NULL; // Populated if inputString != NULL; used otherwise
1801 static UINT32 startPos = 0;
1802 // static UINT32 endPos = 0;
1803 static UINT32 stringLength = 0;
1804 static UINT8 inComment = 0; // 0 = not in comment, 1 = // Single-line, 2 = /* Multi-line */
1805 char *texturesToken = NULL;
1806 UINT32 texturesTokenLength = 0;
1807
1808 if (inputString != NULL)
1809 {
1810 stringToUse = inputString;
1811 startPos = 0;
1812 oldendPos = endPos = 0;
1813 stringLength = strlen(inputString);
1814 }
1815 else
1816 {
1817 startPos = oldendPos = endPos;
1818 }
1819 if (stringToUse == NULL)
1820 return NULL;
1821
1822 // Try to detect comments now, in case we're pointing right at one
1823 if (startPos < stringLength - 1
1824 && inComment == 0)
1825 {
1826 if (stringToUse[startPos] == '/'
1827 && stringToUse[startPos+1] == '/')
1828 {
1829 //Single-line comment start
1830 inComment = 1;
1831 }
1832 else if (stringToUse[startPos] == '/'
1833 && stringToUse[startPos+1] == '*')
1834 {
1835 //Multi-line comment start
1836 inComment = 2;
1837 }
1838 }
1839
1840 // Find the first non-whitespace char, or else the end of the string trying
1841 while ((stringToUse[startPos] == ' '
1842 || stringToUse[startPos] == '\t'
1843 || stringToUse[startPos] == '\r'
1844 || stringToUse[startPos] == '\n'
1845 || stringToUse[startPos] == '\0'
1846 || stringToUse[startPos] == '=' || stringToUse[startPos] == ';' // UDMF TEXTMAP.
1847 || inComment != 0)
1848 && startPos < stringLength)
1849 {
1850 // Try to detect comment endings now
1851 if (inComment == 1
1852 && stringToUse[startPos] == '\n')
1853 {
1854 // End of line for a single-line comment
1855 inComment = 0;
1856 }
1857 else if (inComment == 2
1858 && startPos < stringLength - 1
1859 && stringToUse[startPos] == '*'
1860 && stringToUse[startPos+1] == '/')
1861 {
1862 // End of multi-line comment
1863 inComment = 0;
1864 startPos++; // Make damn well sure we're out of the comment ending at the end of it all
1865 }
1866
1867 startPos++;
1868
1869 // Try to detect comment starts now
1870 if (startPos < stringLength - 1
1871 && inComment == 0)
1872 {
1873 if (stringToUse[startPos] == '/'
1874 && stringToUse[startPos+1] == '/')
1875 {
1876 //Single-line comment start
1877 inComment = 1;
1878 }
1879 else if (stringToUse[startPos] == '/'
1880 && stringToUse[startPos+1] == '*')
1881 {
1882 //Multi-line comment start
1883 inComment = 2;
1884 }
1885 }
1886 }
1887
1888 // If the end of the string is reached, no token is to be read
1889 if (startPos == stringLength) {
1890 endPos = stringLength;
1891 return NULL;
1892 }
1893 // Else, if it's one of these three symbols, capture only this one character
1894 else if (stringToUse[startPos] == ','
1895 || stringToUse[startPos] == '{'
1896 || stringToUse[startPos] == '}')
1897 {
1898 endPos = startPos + 1;
1899 texturesToken = (char *)Z_Malloc(2*sizeof(char),PU_STATIC,NULL);
1900 texturesToken[0] = stringToUse[startPos];
1901 texturesToken[1] = '\0';
1902 return texturesToken;
1903 }
1904 // Return entire string within quotes, except without the quotes.
1905 else if (stringToUse[startPos] == '"')
1906 {
1907 endPos = ++startPos;
1908 while (stringToUse[endPos] != '"' && endPos < stringLength)
1909 endPos++;
1910
1911 texturesTokenLength = endPos++ - startPos;
1912 // Assign the memory. Don't forget an extra byte for the end of the string!
1913 texturesToken = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
1914 // Copy the string.
1915 M_Memcpy(texturesToken, stringToUse+startPos, (size_t)texturesTokenLength);
1916 // Make the final character NUL.
1917 texturesToken[texturesTokenLength] = '\0';
1918
1919 return texturesToken;
1920 }
1921
1922 // Now find the end of the token. This includes several additional characters that are okay to capture as one character, but not trailing at the end of another token.
1923 endPos = startPos + 1;
1924 while ((stringToUse[endPos] != ' '
1925 && stringToUse[endPos] != '\t'
1926 && stringToUse[endPos] != '\r'
1927 && stringToUse[endPos] != '\n'
1928 && stringToUse[endPos] != ','
1929 && stringToUse[endPos] != '{'
1930 && stringToUse[endPos] != '}'
1931 && stringToUse[endPos] != '=' && stringToUse[endPos] != ';' // UDMF TEXTMAP.
1932 && inComment == 0)
1933 && endPos < stringLength)
1934 {
1935 endPos++;
1936 // Try to detect comment starts now; if it's in a comment, we don't want it in this token
1937 if (endPos < stringLength - 1
1938 && inComment == 0)
1939 {
1940 if (stringToUse[endPos] == '/'
1941 && stringToUse[endPos+1] == '/')
1942 {
1943 //Single-line comment start
1944 inComment = 1;
1945 }
1946 else if (stringToUse[endPos] == '/'
1947 && stringToUse[endPos+1] == '*')
1948 {
1949 //Multi-line comment start
1950 inComment = 2;
1951 }
1952 }
1953 }
1954 texturesTokenLength = endPos - startPos;
1955
1956 // Assign the memory. Don't forget an extra byte for the end of the string!
1957 texturesToken = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
1958 // Copy the string.
1959 M_Memcpy(texturesToken, stringToUse+startPos, (size_t)texturesTokenLength);
1960 // Make the final character NUL.
1961 texturesToken[texturesTokenLength] = '\0';
1962 return texturesToken;
1963 }
1964
1965 /** Undoes the last M_GetToken call
1966 * The current position along the string being parsed is reset to the last saved position.
1967 * This exists mostly because of R_ParseTexture/R_ParsePatch honestly, but could be useful elsewhere?
1968 * -Monster Iestyn (22/10/16)
1969 */
M_UnGetToken(void)1970 void M_UnGetToken(void)
1971 {
1972 endPos = oldendPos;
1973 }
1974
1975 /** Returns the current token's position.
1976 */
M_GetTokenPos(void)1977 UINT32 M_GetTokenPos(void)
1978 {
1979 return endPos;
1980 }
1981
1982 /** Sets the current token's position.
1983 */
M_SetTokenPos(UINT32 newPos)1984 void M_SetTokenPos(UINT32 newPos)
1985 {
1986 endPos = newPos;
1987 }
1988
1989 /** Count bits in a number.
1990 */
M_CountBits(UINT32 num,UINT8 size)1991 UINT8 M_CountBits(UINT32 num, UINT8 size)
1992 {
1993 UINT8 i, sum = 0;
1994
1995 for (i = 0; i < size; ++i)
1996 if (num & (1 << i))
1997 ++sum;
1998 return sum;
1999 }
2000
GetRevisionString(void)2001 const char *GetRevisionString(void)
2002 {
2003 static char rev[9] = {0};
2004 if (rev[0])
2005 return rev;
2006
2007 if (comprevision[0] == 'r')
2008 strncpy(rev, comprevision, 7);
2009 else
2010 snprintf(rev, 7, "r%s", comprevision);
2011 rev[7] = '\0';
2012
2013 return rev;
2014 }
2015
2016 // Vector/matrix math
VectorMatrixMultiply(TVector v,TMatrix m)2017 TVector *VectorMatrixMultiply(TVector v, TMatrix m)
2018 {
2019 static TVector ret;
2020
2021 ret[0] = FixedMul(v[0],m[0][0]) + FixedMul(v[1],m[1][0]) + FixedMul(v[2],m[2][0]) + FixedMul(v[3],m[3][0]);
2022 ret[1] = FixedMul(v[0],m[0][1]) + FixedMul(v[1],m[1][1]) + FixedMul(v[2],m[2][1]) + FixedMul(v[3],m[3][1]);
2023 ret[2] = FixedMul(v[0],m[0][2]) + FixedMul(v[1],m[1][2]) + FixedMul(v[2],m[2][2]) + FixedMul(v[3],m[3][2]);
2024 ret[3] = FixedMul(v[0],m[0][3]) + FixedMul(v[1],m[1][3]) + FixedMul(v[2],m[2][3]) + FixedMul(v[3],m[3][3]);
2025
2026 return &ret;
2027 }
2028
RotateXMatrix(angle_t rad)2029 TMatrix *RotateXMatrix(angle_t rad)
2030 {
2031 static TMatrix ret;
2032 const angle_t fa = rad>>ANGLETOFINESHIFT;
2033 const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);
2034
2035 ret[0][0] = FRACUNIT; ret[0][1] = 0; ret[0][2] = 0; ret[0][3] = 0;
2036 ret[1][0] = 0; ret[1][1] = cosrad; ret[1][2] = sinrad; ret[1][3] = 0;
2037 ret[2][0] = 0; ret[2][1] = -sinrad; ret[2][2] = cosrad; ret[2][3] = 0;
2038 ret[3][0] = 0; ret[3][1] = 0; ret[3][2] = 0; ret[3][3] = FRACUNIT;
2039
2040 return &ret;
2041 }
2042
2043 #if 0
2044 TMatrix *RotateYMatrix(angle_t rad)
2045 {
2046 static TMatrix ret;
2047 const angle_t fa = rad>>ANGLETOFINESHIFT;
2048 const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);
2049
2050 ret[0][0] = cosrad; ret[0][1] = 0; ret[0][2] = -sinrad; ret[0][3] = 0;
2051 ret[1][0] = 0; ret[1][1] = FRACUNIT; ret[1][2] = 0; ret[1][3] = 0;
2052 ret[2][0] = sinrad; ret[2][1] = 0; ret[2][2] = cosrad; ret[2][3] = 0;
2053 ret[3][0] = 0; ret[3][1] = 0; ret[3][2] = 0; ret[3][3] = FRACUNIT;
2054
2055 return &ret;
2056 }
2057 #endif
2058
RotateZMatrix(angle_t rad)2059 TMatrix *RotateZMatrix(angle_t rad)
2060 {
2061 static TMatrix ret;
2062 const angle_t fa = rad>>ANGLETOFINESHIFT;
2063 const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);
2064
2065 ret[0][0] = cosrad; ret[0][1] = sinrad; ret[0][2] = 0; ret[0][3] = 0;
2066 ret[1][0] = -sinrad; ret[1][1] = cosrad; ret[1][2] = 0; ret[1][3] = 0;
2067 ret[2][0] = 0; ret[2][1] = 0; ret[2][2] = FRACUNIT; ret[2][3] = 0;
2068 ret[3][0] = 0; ret[3][1] = 0; ret[3][2] = 0; ret[3][3] = FRACUNIT;
2069
2070 return &ret;
2071 }
2072
2073 /** Set of functions to take in a size_t as an argument,
2074 * put the argument in a character buffer, and return the
2075 * pointer to that buffer.
2076 * This is to eliminate usage of PRIdS, so gettext can work
2077 * with *all* of SRB2's strings.
2078 */
sizeu1(size_t num)2079 char *sizeu1(size_t num)
2080 {
2081 static char sizeu1_buf[28];
2082 sprintf(sizeu1_buf, "%"PRIdS, num);
2083 return sizeu1_buf;
2084 }
2085
sizeu2(size_t num)2086 char *sizeu2(size_t num)
2087 {
2088 static char sizeu2_buf[28];
2089 sprintf(sizeu2_buf, "%"PRIdS, num);
2090 return sizeu2_buf;
2091 }
2092
sizeu3(size_t num)2093 char *sizeu3(size_t num)
2094 {
2095 static char sizeu3_buf[28];
2096 sprintf(sizeu3_buf, "%"PRIdS, num);
2097 return sizeu3_buf;
2098 }
2099
sizeu4(size_t num)2100 char *sizeu4(size_t num)
2101 {
2102 static char sizeu4_buf[28];
2103 sprintf(sizeu4_buf, "%"PRIdS, num);
2104 return sizeu4_buf;
2105 }
2106
sizeu5(size_t num)2107 char *sizeu5(size_t num)
2108 {
2109 static char sizeu5_buf[28];
2110 sprintf(sizeu5_buf, "%"PRIdS, num);
2111 return sizeu5_buf;
2112 }
2113
2114 #if defined (__GNUC__) && defined (__i386__) // from libkwave, under GPL
2115 // Alam: note libkwave memcpy code comes from mplayer's libvo/aclib_template.c, r699
2116
2117 /* for small memory blocks (<256 bytes) this version is faster */
2118 #define small_memcpy(dest,src,n)\
2119 {\
2120 register unsigned long int dummy;\
2121 __asm__ __volatile__(\
2122 "cld\n\t"\
2123 "rep; movsb"\
2124 :"=&D"(dest), "=&S"(src), "=&c"(dummy)\
2125 :"0" (dest), "1" (src),"2" (n)\
2126 : "memory", "cc");\
2127 }
2128 /* linux kernel __memcpy (from: /include/asm/string.h) */
__memcpy(void * dest,const void * src,size_t n)2129 ATTRINLINE static FUNCINLINE void *__memcpy (void *dest, const void * src, size_t n)
2130 {
2131 int d0, d1, d2;
2132
2133 if ( n < 4 )
2134 {
2135 small_memcpy(dest, src, n);
2136 }
2137 else
2138 {
2139 __asm__ __volatile__ (
2140 "rep ; movsl;"
2141 "testb $2,%b4;"
2142 "je 1f;"
2143 "movsw;"
2144 "1:\ttestb $1,%b4;"
2145 "je 2f;"
2146 "movsb;"
2147 "2:"
2148 : "=&c" (d0), "=&D" (d1), "=&S" (d2)
2149 :"0" (n/4), "q" (n),"1" ((long) dest),"2" ((long) src)
2150 : "memory");
2151 }
2152
2153 return dest;
2154 }
2155
2156 #define SSE_MMREG_SIZE 16
2157 #define MMX_MMREG_SIZE 8
2158
2159 #define MMX1_MIN_LEN 0x800 /* 2K blocks */
2160 #define MIN_LEN 0x40 /* 64-byte blocks */
2161
2162 /* SSE note: i tried to move 128 bytes a time instead of 64 but it
2163 didn't make any measureable difference. i'm using 64 for the sake of
2164 simplicity. [MF] */
sse_cpy(void * dest,const void * src,size_t n)2165 static /*FUNCTARGET("sse2")*/ void *sse_cpy(void * dest, const void * src, size_t n)
2166 {
2167 void *retval = dest;
2168 size_t i;
2169
2170 /* PREFETCH has effect even for MOVSB instruction ;) */
2171 __asm__ __volatile__ (
2172 "prefetchnta (%0);"
2173 "prefetchnta 32(%0);"
2174 "prefetchnta 64(%0);"
2175 "prefetchnta 96(%0);"
2176 "prefetchnta 128(%0);"
2177 "prefetchnta 160(%0);"
2178 "prefetchnta 192(%0);"
2179 "prefetchnta 224(%0);"
2180 "prefetchnta 256(%0);"
2181 "prefetchnta 288(%0);"
2182 : : "r" (src) );
2183
2184 if (n >= MIN_LEN)
2185 {
2186 register unsigned long int delta;
2187 /* Align destinition to MMREG_SIZE -boundary */
2188 delta = ((unsigned long int)dest)&(SSE_MMREG_SIZE-1);
2189 if (delta)
2190 {
2191 delta=SSE_MMREG_SIZE-delta;
2192 n -= delta;
2193 small_memcpy(dest, src, delta);
2194 }
2195 i = n >> 6; /* n/64 */
2196 n&=63;
2197 if (((unsigned long)src) & 15)
2198 /* if SRC is misaligned */
2199 for (; i>0; i--)
2200 {
2201 __asm__ __volatile__ (
2202 "prefetchnta 320(%0);"
2203 "prefetchnta 352(%0);"
2204 "movups (%0), %%xmm0;"
2205 "movups 16(%0), %%xmm1;"
2206 "movups 32(%0), %%xmm2;"
2207 "movups 48(%0), %%xmm3;"
2208 "movntps %%xmm0, (%1);"
2209 "movntps %%xmm1, 16(%1);"
2210 "movntps %%xmm2, 32(%1);"
2211 "movntps %%xmm3, 48(%1);"
2212 :: "r" (src), "r" (dest) : "memory");
2213 src = (const unsigned char *)src + 64;
2214 dest = (unsigned char *)dest + 64;
2215 }
2216 else
2217 /*
2218 Only if SRC is aligned on 16-byte boundary.
2219 It allows to use movaps instead of movups, which required data
2220 to be aligned or a general-protection exception (#GP) is generated.
2221 */
2222 for (; i>0; i--)
2223 {
2224 __asm__ __volatile__ (
2225 "prefetchnta 320(%0);"
2226 "prefetchnta 352(%0);"
2227 "movaps (%0), %%xmm0;"
2228 "movaps 16(%0), %%xmm1;"
2229 "movaps 32(%0), %%xmm2;"
2230 "movaps 48(%0), %%xmm3;"
2231 "movntps %%xmm0, (%1);"
2232 "movntps %%xmm1, 16(%1);"
2233 "movntps %%xmm2, 32(%1);"
2234 "movntps %%xmm3, 48(%1);"
2235 :: "r" (src), "r" (dest) : "memory");
2236 src = ((const unsigned char *)src) + 64;
2237 dest = ((unsigned char *)dest) + 64;
2238 }
2239 /* since movntq is weakly-ordered, a "sfence"
2240 * is needed to become ordered again. */
2241 __asm__ __volatile__ ("sfence":::"memory");
2242 /* enables to use FPU */
2243 __asm__ __volatile__ ("emms":::"memory");
2244 }
2245 /*
2246 * Now do the tail of the block
2247 */
2248 if (n) __memcpy(dest, src, n);
2249 return retval;
2250 }
2251
mmx2_cpy(void * dest,const void * src,size_t n)2252 static FUNCTARGET("mmx") void *mmx2_cpy(void *dest, const void *src, size_t n)
2253 {
2254 void *retval = dest;
2255 size_t i;
2256
2257 /* PREFETCH has effect even for MOVSB instruction ;) */
2258 __asm__ __volatile__ (
2259 "prefetchnta (%0);"
2260 "prefetchnta 32(%0);"
2261 "prefetchnta 64(%0);"
2262 "prefetchnta 96(%0);"
2263 "prefetchnta 128(%0);"
2264 "prefetchnta 160(%0);"
2265 "prefetchnta 192(%0);"
2266 "prefetchnta 224(%0);"
2267 "prefetchnta 256(%0);"
2268 "prefetchnta 288(%0);"
2269 : : "r" (src));
2270
2271 if (n >= MIN_LEN)
2272 {
2273 register unsigned long int delta;
2274 /* Align destinition to MMREG_SIZE -boundary */
2275 delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1);
2276 if (delta)
2277 {
2278 delta=MMX_MMREG_SIZE-delta;
2279 n -= delta;
2280 small_memcpy(dest, src, delta);
2281 }
2282 i = n >> 6; /* n/64 */
2283 n&=63;
2284 for (; i>0; i--)
2285 {
2286 __asm__ __volatile__ (
2287 "prefetchnta 320(%0);"
2288 "prefetchnta 352(%0);"
2289 "movq (%0), %%mm0;"
2290 "movq 8(%0), %%mm1;"
2291 "movq 16(%0), %%mm2;"
2292 "movq 24(%0), %%mm3;"
2293 "movq 32(%0), %%mm4;"
2294 "movq 40(%0), %%mm5;"
2295 "movq 48(%0), %%mm6;"
2296 "movq 56(%0), %%mm7;"
2297 "movntq %%mm0, (%1);"
2298 "movntq %%mm1, 8(%1);"
2299 "movntq %%mm2, 16(%1);"
2300 "movntq %%mm3, 24(%1);"
2301 "movntq %%mm4, 32(%1);"
2302 "movntq %%mm5, 40(%1);"
2303 "movntq %%mm6, 48(%1);"
2304 "movntq %%mm7, 56(%1);"
2305 :: "r" (src), "r" (dest) : "memory");
2306 src = ((const unsigned char *)src) + 64;
2307 dest = ((unsigned char *)dest) + 64;
2308 }
2309 /* since movntq is weakly-ordered, a "sfence"
2310 * is needed to become ordered again. */
2311 __asm__ __volatile__ ("sfence":::"memory");
2312 __asm__ __volatile__ ("emms":::"memory");
2313 }
2314 /*
2315 * Now do the tail of the block
2316 */
2317 if (n) __memcpy(dest, src, n);
2318 return retval;
2319 }
2320
mmx1_cpy(void * dest,const void * src,size_t n)2321 static FUNCTARGET("mmx") void *mmx1_cpy(void *dest, const void *src, size_t n) //3DNOW
2322 {
2323 void *retval = dest;
2324 size_t i;
2325
2326 /* PREFETCH has effect even for MOVSB instruction ;) */
2327 __asm__ __volatile__ (
2328 "prefetch (%0);"
2329 "prefetch 32(%0);"
2330 "prefetch 64(%0);"
2331 "prefetch 96(%0);"
2332 "prefetch 128(%0);"
2333 "prefetch 160(%0);"
2334 "prefetch 192(%0);"
2335 "prefetch 224(%0);"
2336 "prefetch 256(%0);"
2337 "prefetch 288(%0);"
2338 : : "r" (src));
2339
2340 if (n >= MMX1_MIN_LEN)
2341 {
2342 register unsigned long int delta;
2343 /* Align destinition to MMREG_SIZE -boundary */
2344 delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1);
2345 if (delta)
2346 {
2347 delta=MMX_MMREG_SIZE-delta;
2348 n -= delta;
2349 small_memcpy(dest, src, delta);
2350 }
2351 i = n >> 6; /* n/64 */
2352 n&=63;
2353 for (; i>0; i--)
2354 {
2355 __asm__ __volatile__ (
2356 "prefetch 320(%0);"
2357 "prefetch 352(%0);"
2358 "movq (%0), %%mm0;"
2359 "movq 8(%0), %%mm1;"
2360 "movq 16(%0), %%mm2;"
2361 "movq 24(%0), %%mm3;"
2362 "movq 32(%0), %%mm4;"
2363 "movq 40(%0), %%mm5;"
2364 "movq 48(%0), %%mm6;"
2365 "movq 56(%0), %%mm7;"
2366 "movq %%mm0, (%1);"
2367 "movq %%mm1, 8(%1);"
2368 "movq %%mm2, 16(%1);"
2369 "movq %%mm3, 24(%1);"
2370 "movq %%mm4, 32(%1);"
2371 "movq %%mm5, 40(%1);"
2372 "movq %%mm6, 48(%1);"
2373 "movq %%mm7, 56(%1);"
2374 :: "r" (src), "r" (dest) : "memory");
2375 src = ((const unsigned char *)src) + 64;
2376 dest = ((unsigned char *)dest) + 64;
2377 }
2378 __asm__ __volatile__ ("femms":::"memory"); // same as mmx_cpy() but with a femms
2379 }
2380 /*
2381 * Now do the tail of the block
2382 */
2383 if (n) __memcpy(dest, src, n);
2384 return retval;
2385 }
2386 #endif
2387
2388 // Alam: why? memcpy may be __cdecl/_System and our code may be not the same type
cpu_cpy(void * dest,const void * src,size_t n)2389 static void *cpu_cpy(void *dest, const void *src, size_t n)
2390 {
2391 if (src == NULL)
2392 {
2393 CONS_Debug(DBG_MEMORY, "Memcpy from 0x0?!: %p %p %s\n", dest, src, sizeu1(n));
2394 return dest;
2395 }
2396
2397 if(dest == NULL)
2398 {
2399 CONS_Debug(DBG_MEMORY, "Memcpy to 0x0?!: %p %p %s\n", dest, src, sizeu1(n));
2400 return dest;
2401 }
2402
2403 return memcpy(dest, src, n);
2404 }
2405
mmx_cpy(void * dest,const void * src,size_t n)2406 static /*FUNCTARGET("mmx")*/ void *mmx_cpy(void *dest, const void *src, size_t n)
2407 {
2408 #if defined (_MSC_VER) && defined (_X86_)
2409 _asm
2410 {
2411 mov ecx, [n]
2412 mov esi, [src]
2413 mov edi, [dest]
2414 shr ecx, 6 // mit mmx: 64bytes per iteration
2415 jz lower_64 // if lower than 64 bytes
2416 loop_64: // MMX transfers multiples of 64bytes
2417 movq mm0, 0[ESI] // read sources
2418 movq mm1, 8[ESI]
2419 movq mm2, 16[ESI]
2420 movq mm3, 24[ESI]
2421 movq mm4, 32[ESI]
2422 movq mm5, 40[ESI]
2423 movq mm6, 48[ESI]
2424 movq mm7, 56[ESI]
2425
2426 movq 0[EDI], mm0 // write destination
2427 movq 8[EDI], mm1
2428 movq 16[EDI], mm2
2429 movq 24[EDI], mm3
2430 movq 32[EDI], mm4
2431 movq 40[EDI], mm5
2432 movq 48[EDI], mm6
2433 movq 56[EDI], mm7
2434
2435 add esi, 64
2436 add edi, 64
2437 dec ecx
2438 jnz loop_64
2439 emms // close mmx operation
2440 lower_64:// transfer rest of buffer
2441 mov ebx,esi
2442 sub ebx,src
2443 mov ecx,[n]
2444 sub ecx,ebx
2445 shr ecx, 3 // multiples of 8 bytes
2446 jz lower_8
2447 loop_8:
2448 movq mm0, [esi] // read source
2449 movq [edi], mm0 // write destination
2450 add esi, 8
2451 add edi, 8
2452 dec ecx
2453 jnz loop_8
2454 emms // close mmx operation
2455 lower_8:
2456 mov ebx,esi
2457 sub ebx,src
2458 mov ecx,[n]
2459 sub ecx,ebx
2460 rep movsb
2461 mov eax, [dest] // return dest
2462 }
2463 #elif defined (__GNUC__) && defined (__i386__)
2464 void *retval = dest;
2465 size_t i;
2466
2467 if (n >= MMX1_MIN_LEN)
2468 {
2469 register unsigned long int delta;
2470 /* Align destinition to MMREG_SIZE -boundary */
2471 delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1);
2472 if (delta)
2473 {
2474 delta=MMX_MMREG_SIZE-delta;
2475 n -= delta;
2476 small_memcpy(dest, src, delta);
2477 }
2478 i = n >> 6; /* n/64 */
2479 n&=63;
2480 for (; i>0; i--)
2481 {
2482 __asm__ __volatile__ (
2483 "movq (%0), %%mm0;"
2484 "movq 8(%0), %%mm1;"
2485 "movq 16(%0), %%mm2;"
2486 "movq 24(%0), %%mm3;"
2487 "movq 32(%0), %%mm4;"
2488 "movq 40(%0), %%mm5;"
2489 "movq 48(%0), %%mm6;"
2490 "movq 56(%0), %%mm7;"
2491 "movq %%mm0, (%1);"
2492 "movq %%mm1, 8(%1);"
2493 "movq %%mm2, 16(%1);"
2494 "movq %%mm3, 24(%1);"
2495 "movq %%mm4, 32(%1);"
2496 "movq %%mm5, 40(%1);"
2497 "movq %%mm6, 48(%1);"
2498 "movq %%mm7, 56(%1);"
2499 :: "r" (src), "r" (dest) : "memory");
2500 src = ((const unsigned char *)src) + 64;
2501 dest = ((unsigned char *)dest) + 64;
2502 }
2503 __asm__ __volatile__ ("emms":::"memory");
2504 }
2505 /*
2506 * Now do the tail of the block
2507 */
2508 if (n) __memcpy(dest, src, n);
2509 return retval;
2510 #else
2511 return cpu_cpy(dest, src, n);
2512 #endif
2513 }
2514
2515 void *(*M_Memcpy)(void* dest, const void* src, size_t n) = cpu_cpy;
2516
2517 /** Memcpy that uses MMX, 3DNow, MMXExt or even SSE
2518 * Do not use on overlapped memory, use memmove for that
2519 */
M_SetupMemcpy(void)2520 void M_SetupMemcpy(void)
2521 {
2522 #if defined (__GNUC__) && defined (__i386__)
2523 if (R_SSE2)
2524 M_Memcpy = sse_cpy;
2525 else if (R_MMXExt)
2526 M_Memcpy = mmx2_cpy;
2527 else if (R_3DNow)
2528 M_Memcpy = mmx1_cpy;
2529 else
2530 #endif
2531 if (R_MMX)
2532 M_Memcpy = mmx_cpy;
2533 #if 0
2534 M_Memcpy = cpu_cpy;
2535 #endif
2536 }
2537
2538 /** Return the appropriate message for a file error or end of file.
2539 */
M_FileError(FILE * fp)2540 const char *M_FileError(FILE *fp)
2541 {
2542 if (ferror(fp))
2543 return strerror(errno);
2544 else
2545 return "end-of-file";
2546 }
2547
2548 /** Return the number of parts of this path.
2549 */
M_PathParts(const char * path)2550 int M_PathParts(const char *path)
2551 {
2552 int n;
2553 const char *p;
2554 const char *t;
2555 if (path == NULL)
2556 return 0;
2557 for (n = 0, p = path ;; ++n)
2558 {
2559 t = p;
2560 if (( p = strchr(p, PATHSEP[0]) ))
2561 p += strspn(p, PATHSEP);
2562 else
2563 {
2564 if (*t)/* there is something after the final delimiter */
2565 n++;
2566 break;
2567 }
2568 }
2569 return n;
2570 }
2571
2572 /** Check whether a path is an absolute path.
2573 */
M_IsPathAbsolute(const char * path)2574 boolean M_IsPathAbsolute(const char *path)
2575 {
2576 #ifdef _WIN32
2577 return ( strncmp(&path[1], ":\\", 2) == 0 );
2578 #else
2579 return ( path[0] == '/' );
2580 #endif
2581 }
2582
2583 /** I_mkdir for each part of the path.
2584 */
M_MkdirEachUntil(const char * cpath,int start,int end,int mode)2585 void M_MkdirEachUntil(const char *cpath, int start, int end, int mode)
2586 {
2587 char path[MAX_WADPATH];
2588 char *p;
2589 char *t;
2590
2591 if (end > 0 && end <= start)
2592 return;
2593
2594 strlcpy(path, cpath, sizeof path);
2595 #ifdef _WIN32
2596 if (strncmp(&path[1], ":\\", 2) == 0)
2597 p = &path[3];
2598 else
2599 #endif
2600 p = path;
2601
2602 if (end > 0)
2603 end -= start;
2604
2605 for (; start > 0; --start)
2606 {
2607 p += strspn(p, PATHSEP);
2608 if (!( p = strchr(p, PATHSEP[0]) ))
2609 return;
2610 }
2611 p += strspn(p, PATHSEP);
2612 for (;;)
2613 {
2614 if (end > 0 && !--end)
2615 break;
2616
2617 t = p;
2618 if (( p = strchr(p, PATHSEP[0]) ))
2619 {
2620 *p = '\0';
2621 I_mkdir(path, mode);
2622 *p = PATHSEP[0];
2623 p += strspn(p, PATHSEP);
2624 }
2625 else
2626 {
2627 if (*t)
2628 I_mkdir(path, mode);
2629 break;
2630 }
2631 }
2632 }
2633
M_MkdirEach(const char * path,int start,int mode)2634 void M_MkdirEach(const char *path, int start, int mode)
2635 {
2636 M_MkdirEachUntil(path, start, -1, mode);
2637 }
2638
M_JumpWord(const char * line)2639 int M_JumpWord(const char *line)
2640 {
2641 int c;
2642
2643 c = line[0];
2644
2645 if (isspace(c))
2646 return strspn(line, " ");
2647 else if (ispunct(c))
2648 return strspn(line, PUNCTUATION);
2649 else
2650 {
2651 if (isspace(line[1]))
2652 return 1 + strspn(&line[1], " ");
2653 else
2654 return strcspn(line, " "PUNCTUATION);
2655 }
2656 }
2657
M_JumpWordReverse(const char * line,int offset)2658 int M_JumpWordReverse(const char *line, int offset)
2659 {
2660 int (*is)(int);
2661 int c;
2662 c = line[--offset];
2663 if (isspace(c))
2664 is = isspace;
2665 else if (ispunct(c))
2666 is = ispunct;
2667 else
2668 is = isalnum;
2669 c = (*is)(line[offset]);
2670 while (offset > 0 &&
2671 (*is)(line[offset - 1]) == c)
2672 offset--;
2673 return offset;
2674 }
2675
M_Ftrim(double f)2676 const char * M_Ftrim (double f)
2677 {
2678 static char dig[9];/* "0." + 6 digits (6 is printf's default) */
2679 int i;
2680 /* I know I said it's the default, but just in case... */
2681 sprintf(dig, "%.6f", fabs(modf(f, &f)));
2682 /* trim trailing zeroes */
2683 for (i = strlen(dig)-1; dig[i] == '0'; --i)
2684 ;
2685 if (dig[i] == '.')/* :NOTHING: */
2686 return "";
2687 else
2688 {
2689 dig[i + 1] = '\0';
2690 return &dig[1];/* skip the 0 */
2691 }
2692 }
2693