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