1 // Emacs style mode select	 -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id:$
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 //
18 // $Log:$
19 //
20 // DESCRIPTION:
21 //		Default Config File.
22 //		Screenshots.
23 //
24 //-----------------------------------------------------------------------------
25 
26 
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <fcntl.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <stdlib.h>
33 #include <time.h>
34 
35 #include "doomtype.h"
36 #include "version.h"
37 
38 #if defined(_WIN32)
39 #include <io.h>
40 #else
41 #include <unistd.h>
42 #endif
43 
44 #include <ctype.h>
45 
46 #include "doomdef.h"
47 
48 #include "m_swap.h"
49 #include "m_argv.h"
50 
51 #include "w_wad.h"
52 
53 #include "c_cvars.h"
54 #include "c_dispatch.h"
55 #include "c_bind.h"
56 
57 #include "i_system.h"
58 #include "i_video.h"
59 #include "v_video.h"
60 #include "r_defs.h"
61 
62 #include "hu_stuff.h"
63 
64 // State.
65 #include "doomstat.h"
66 
67 // Data.
68 #include "m_misc.h"
69 #include "m_png.h"
70 
71 #include "cmdlib.h"
72 
73 #include "g_game.h"
74 #include "gi.h"
75 
76 #include "gameconfigfile.h"
77 
78 FGameConfigFile *GameConfig;
79 
80 CVAR(Bool, screenshot_quiet, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
81 CVAR(String, screenshot_type, "png", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
82 CVAR(String, screenshot_dir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
83 EXTERN_CVAR(Bool, longsavemessages);
84 
85 static long ParseCommandLine (const char *args, int *argc, char **argv);
86 
87 //
88 // M_WriteFile
89 //
90 #ifndef O_BINARY
91 #define O_BINARY 0
92 #endif
93 
M_WriteFile(char const * name,void * source,int length)94 bool M_WriteFile (char const *name, void *source, int length)
95 {
96 	int handle;
97 	int count;
98 
99 	handle = open ( name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
100 
101 	if (handle == -1)
102 		return false;
103 
104 	count = write (handle, source, length);
105 	close (handle);
106 
107 	if (count < length)
108 		return false;
109 
110 	return true;
111 }
112 
113 
114 //
115 // M_ReadFile
116 //
M_ReadFile(char const * name,BYTE ** buffer)117 int M_ReadFile (char const *name, BYTE **buffer)
118 {
119 	int handle, count, length;
120 	struct stat fileinfo;
121 	BYTE *buf;
122 
123 	handle = open (name, O_RDONLY | O_BINARY, 0666);
124 	if (handle == -1)
125 		I_Error ("Couldn't read file %s", name);
126 	if (fstat (handle,&fileinfo) == -1)
127 		I_Error ("Couldn't read file %s", name);
128 	length = fileinfo.st_size;
129 	buf = new BYTE[length];
130 	count = read (handle, buf, length);
131 	close (handle);
132 
133 	if (count < length)
134 		I_Error ("Couldn't read file %s", name);
135 
136 	*buffer = buf;
137 	return length;
138 }
139 
140 //
141 // M_ReadFile (same as above but use malloc instead of new to allocate the buffer.)
142 //
M_ReadFileMalloc(char const * name,BYTE ** buffer)143 int M_ReadFileMalloc (char const *name, BYTE **buffer)
144 {
145 	int handle, count, length;
146 	struct stat fileinfo;
147 	BYTE *buf;
148 
149 	handle = open (name, O_RDONLY | O_BINARY, 0666);
150 	if (handle == -1)
151 		I_Error ("Couldn't read file %s", name);
152 	if (fstat (handle,&fileinfo) == -1)
153 		I_Error ("Couldn't read file %s", name);
154 	length = fileinfo.st_size;
155 	buf = (BYTE*)M_Malloc(length);
156 	count = read (handle, buf, length);
157 	close (handle);
158 
159 	if (count < length)
160 		I_Error ("Couldn't read file %s", name);
161 
162 	*buffer = buf;
163 	return length;
164 }
165 
166 //---------------------------------------------------------------------------
167 //
168 // PROC M_FindResponseFile
169 //
170 //---------------------------------------------------------------------------
171 
M_FindResponseFile(void)172 void M_FindResponseFile (void)
173 {
174 	const int limit = 100;	// avoid infinite recursion
175 	int added_stuff = 0;
176 	int i = 1;
177 
178 	while (i < Args->NumArgs())
179 	{
180 		if (Args->GetArg(i)[0] != '@')
181 		{
182 			i++;
183 		}
184 		else
185 		{
186 			char	**argv;
187 			char	*file = NULL;
188 			int		argc = 0;
189 			FILE	*handle;
190 			int 	size;
191 			long	argsize = 0;
192 			int 	index;
193 
194 			// Any more response files after the limit will be removed from the
195 			// command line.
196 			if (added_stuff < limit)
197 			{
198 				// READ THE RESPONSE FILE INTO MEMORY
199 				handle = fopen (Args->GetArg(i) + 1,"rb");
200 				if (!handle)
201 				{ // [RH] Make this a warning, not an error.
202 					Printf ("No such response file (%s)!\n", Args->GetArg(i) + 1);
203 				}
204 				else
205 				{
206 					Printf ("Found response file %s!\n", Args->GetArg(i) + 1);
207 					fseek (handle, 0, SEEK_END);
208 					size = ftell (handle);
209 					fseek (handle, 0, SEEK_SET);
210 					file = new char[size+1];
211 					fread (file, size, 1, handle);
212 					file[size] = 0;
213 					fclose (handle);
214 
215 					argsize = ParseCommandLine (file, &argc, NULL);
216 				}
217 			}
218 			else
219 			{
220 				Printf ("Ignored response file %s.\n", Args->GetArg(i) + 1);
221 			}
222 
223 			if (argc != 0)
224 			{
225 				argv = (char **)M_Malloc (argc*sizeof(char *) + argsize);
226 				argv[0] = (char *)argv + argc*sizeof(char *);
227 				ParseCommandLine (file, NULL, argv);
228 
229 				// Create a new argument vector
230 				DArgs *newargs = new DArgs;
231 
232 				// Copy parameters before response file.
233 				for (index = 0; index < i; ++index)
234 					newargs->AppendArg(Args->GetArg(index));
235 
236 				// Copy parameters from response file.
237 				for (index = 0; index < argc; ++index)
238 					newargs->AppendArg(argv[index]);
239 
240 				// Copy parameters after response file.
241 				for (index = i + 1; index < Args->NumArgs(); ++index)
242 					newargs->AppendArg(Args->GetArg(index));
243 
244 				// Use the new argument vector as the global Args object.
245 				Args = newargs;
246 				if (++added_stuff == limit)
247 				{
248 					Printf("Response file limit of %d hit.\n", limit);
249 				}
250 			}
251 			else
252 			{
253 				// Remove the response file from the Args object
254 				Args->RemoveArg(i);
255 			}
256 			if (file != NULL)
257 			{
258 				delete[] file;
259 			}
260 		}
261 	}
262 	if (added_stuff > 0)
263 	{
264 		// DISPLAY ARGS
265 		Printf ("Added %d response file%s, now have %d command-line args:\n",
266 			added_stuff, added_stuff > 1 ? "s" : "", Args->NumArgs ());
267 		for (int k = 1; k < Args->NumArgs (); k++)
268 			Printf ("%s\n", Args->GetArg (k));
269 	}
270 }
271 
272 // ParseCommandLine
273 //
274 // This is just like the version in c_dispatch.cpp, except it does not
275 // do cvar expansion.
276 
ParseCommandLine(const char * args,int * argc,char ** argv)277 static long ParseCommandLine (const char *args, int *argc, char **argv)
278 {
279 	int count;
280 	char *buffplace;
281 
282 	count = 0;
283 	buffplace = NULL;
284 	if (argv != NULL)
285 	{
286 		buffplace = argv[0];
287 	}
288 
289 	for (;;)
290 	{
291 		while (*args <= ' ' && *args)
292 		{ // skip white space
293 			args++;
294 		}
295 		if (*args == 0)
296 		{
297 			break;
298 		}
299 		else if (*args == '\"')
300 		{ // read quoted string
301 			char stuff;
302 			if (argv != NULL)
303 			{
304 				argv[count] = buffplace;
305 			}
306 			count++;
307 			args++;
308 			do
309 			{
310 				stuff = *args++;
311 				if (stuff == '\\' && *args == '\"')
312 				{
313 					stuff = '\"', args++;
314 				}
315 				else if (stuff == '\"')
316 				{
317 					stuff = 0;
318 				}
319 				else if (stuff == 0)
320 				{
321 					args--;
322 				}
323 				if (argv != NULL)
324 				{
325 					*buffplace = stuff;
326 				}
327 				buffplace++;
328 			} while (stuff);
329 		}
330 		else
331 		{ // read unquoted string
332 			const char *start = args++, *end;
333 
334 			while (*args && *args > ' ' && *args != '\"')
335 				args++;
336 			end = args;
337 			if (argv != NULL)
338 			{
339 				argv[count] = buffplace;
340 				while (start < end)
341 					*buffplace++ = *start++;
342 				*buffplace++ = 0;
343 			}
344 			else
345 			{
346 				buffplace += end - start + 1;
347 			}
348 			count++;
349 		}
350 	}
351 	if (argc != NULL)
352 	{
353 		*argc = count;
354 	}
355 	return (long)(buffplace - (char *)0);
356 }
357 
358 
359 //
360 // M_SaveDefaults
361 //
362 
M_SaveDefaults(const char * filename)363 bool M_SaveDefaults (const char *filename)
364 {
365 	FString oldpath;
366 	bool success;
367 
368 	if (filename != NULL)
369 	{
370 		oldpath = GameConfig->GetPathName();
371 		GameConfig->ChangePathName (filename);
372 	}
373 	GameConfig->ArchiveGlobalData ();
374 	if (gameinfo.ConfigName.IsNotEmpty())
375 	{
376 		GameConfig->ArchiveGameData (gameinfo.ConfigName);
377 	}
378 	success = GameConfig->WriteConfigFile ();
379 	if (filename != NULL)
380 	{
381 		GameConfig->ChangePathName (filename);
382 	}
383 	return success;
384 }
385 
M_SaveDefaultsFinal()386 void M_SaveDefaultsFinal ()
387 {
388 	while (!M_SaveDefaults (NULL) && I_WriteIniFailed ())
389 	{
390 		/* Loop until the config saves or I_WriteIniFailed() returns false */
391 	}
392 	delete GameConfig;
393 	GameConfig = NULL;
394 }
395 
CCMD(writeini)396 CCMD (writeini)
397 {
398 	const char *filename = (argv.argc() == 1) ? NULL : argv[1];
399 	if (!M_SaveDefaults (filename))
400 	{
401 		Printf ("Writing config failed: %s\n", strerror(errno));
402 	}
403 	else
404 	{
405 		Printf ("Config saved.\n");
406 	}
407 }
408 
409 //
410 // M_LoadDefaults
411 //
412 
M_LoadDefaults()413 void M_LoadDefaults ()
414 {
415 	GameConfig = new FGameConfigFile;
416 	GameConfig->DoGlobalSetup ();
417 	atterm (M_SaveDefaultsFinal);
418 }
419 
420 
421 //
422 // SCREEN SHOTS
423 //
424 
425 
426 struct pcx_t
427 {
428 	char				manufacturer;
429 	char				version;
430 	char				encoding;
431 	char				bits_per_pixel;
432 
433 	unsigned short		xmin;
434 	unsigned short		ymin;
435 	unsigned short		xmax;
436 	unsigned short		ymax;
437 
438 	unsigned short		hdpi;
439 	unsigned short		vdpi;
440 
441 	unsigned char		palette[48];
442 
443 	char				reserved;
444 	char				color_planes;
445 	unsigned short		bytes_per_line;
446 	unsigned short		palette_type;
447 
448 	char				filler[58];
449 };
450 
451 
452 //
453 // WritePCXfile
454 //
WritePCXfile(FILE * file,const BYTE * buffer,const PalEntry * palette,ESSType color_type,int width,int height,int pitch)455 void WritePCXfile (FILE *file, const BYTE *buffer, const PalEntry *palette,
456 				   ESSType color_type, int width, int height, int pitch)
457 {
458 	BYTE temprow[MAXWIDTH * 3];
459 	const BYTE *data;
460 	int x, y;
461 	int runlen;
462 	int bytes_per_row_minus_one;
463 	BYTE color;
464 	pcx_t pcx;
465 
466 	pcx.manufacturer = 10;				// PCX id
467 	pcx.version = 5;					// 256 (or more) colors
468 	pcx.encoding = 1;
469 	pcx.bits_per_pixel = 8;				// 256 (or more) colors
470 	pcx.xmin = 0;
471 	pcx.ymin = 0;
472 	pcx.xmax = LittleShort((unsigned short)(width-1));
473 	pcx.ymax = LittleShort((unsigned short)(height-1));
474 	pcx.hdpi = LittleShort((unsigned short)75);
475 	pcx.vdpi = LittleShort((unsigned short)75);
476 	memset (pcx.palette, 0, sizeof(pcx.palette));
477 	pcx.reserved = 0;
478 	pcx.color_planes = (color_type == SS_PAL) ? 1 : 3;	// chunky image
479 	pcx.bytes_per_line = width + (width & 1);
480 	pcx.palette_type = 1;				// not a grey scale
481 	memset (pcx.filler, 0, sizeof(pcx.filler));
482 
483 	fwrite (&pcx, 128, 1, file);
484 
485 	bytes_per_row_minus_one = ((color_type == SS_PAL) ? width : width * 3) - 1;
486 
487 	// pack the image
488 	for (y = height; y > 0; y--)
489 	{
490 		switch (color_type)
491 		{
492 		case SS_PAL:
493 			data = buffer;
494 			break;
495 
496 		case SS_RGB:
497 			// Unpack RGB into separate planes.
498 			for (int i = 0; i < width; ++i)
499 			{
500 				temprow[i            ] = buffer[i*3];
501 				temprow[i + width    ] = buffer[i*3 + 1];
502 				temprow[i + width * 2] = buffer[i*3 + 2];
503 			}
504 			data = temprow;
505 			break;
506 
507 		case SS_BGRA:
508 			// Unpack RGB into separate planes, discarding A.
509 			for (int i = 0; i < width; ++i)
510 			{
511 				temprow[i            ] = buffer[i*4 + 2];
512 				temprow[i + width    ] = buffer[i*4 + 1];
513 				temprow[i + width * 2] = buffer[i*4];
514 			}
515 			data = temprow;
516 			break;
517 
518 		default:
519 			// Should never happen.
520 			return;
521 		}
522 		buffer += pitch;
523 
524 		color = *data++;
525 		runlen = 1;
526 
527 		for (x = bytes_per_row_minus_one; x > 0; x--)
528 		{
529 			if (*data == color)
530 			{
531 				runlen++;
532 			}
533 			else
534 			{
535 				if (runlen > 1 || color >= 0xc0)
536 				{
537 					while (runlen > 63)
538 					{
539 						putc (0xff, file);
540 						putc (color, file);
541 						runlen -= 63;
542 					}
543 					if (runlen > 0)
544 					{
545 						putc (0xc0 + runlen, file);
546 					}
547 				}
548 				if (runlen > 0)
549 				{
550 					putc (color, file);
551 				}
552 				runlen = 1;
553 				color = *data;
554 			}
555 			data++;
556 		}
557 
558 		if (runlen > 1 || color >= 0xc0)
559 		{
560 			while (runlen > 63)
561 			{
562 				putc (0xff, file);
563 				putc (color, file);
564 				runlen -= 63;
565 			}
566 			if (runlen > 0)
567 			{
568 				putc (0xc0 + runlen, file);
569 			}
570 		}
571 		if (runlen > 0)
572 		{
573 			putc (color, file);
574 		}
575 
576 		if (width & 1)
577 			putc (0, file);
578 	}
579 
580 	// write the palette
581 	if (color_type == SS_PAL)
582 	{
583 		putc (12, file);		// palette ID byte
584 		for (x = 0; x < 256; x++, palette++)
585 		{
586 			putc (palette->r, file);
587 			putc (palette->g, file);
588 			putc (palette->b, file);
589 		}
590 	}
591 }
592 
593 //
594 // WritePNGfile
595 //
WritePNGfile(FILE * file,const BYTE * buffer,const PalEntry * palette,ESSType color_type,int width,int height,int pitch)596 void WritePNGfile (FILE *file, const BYTE *buffer, const PalEntry *palette,
597 				   ESSType color_type, int width, int height, int pitch)
598 {
599 	char software[100];
600 	mysnprintf(software, countof(software), GAMENAME " %s", GetVersionString());
601 	if (!M_CreatePNG (file, buffer, palette, color_type, width, height, pitch) ||
602 		!M_AppendPNGText (file, "Software", software) ||
603 		!M_FinishPNG (file))
604 	{
605 		Printf ("Could not create screenshot.\n");
606 	}
607 }
608 
609 
610 //
611 // M_ScreenShot
612 //
FindFreeName(FString & fullname,const char * extension)613 static bool FindFreeName (FString &fullname, const char *extension)
614 {
615 	FString lbmname;
616 	int i;
617 
618 	for (i = 0; i <= 9999; i++)
619 	{
620 		const char *gamename = gameinfo.ConfigName;
621 
622 		time_t now;
623 		tm *tm;
624 
625 		time(&now);
626 		tm = localtime(&now);
627 
628 		if (tm == NULL)
629 		{
630 			lbmname.Format ("%sScreenshot_%s_%04d.%s", fullname.GetChars(), gamename, i, extension);
631 		}
632 		else if (i == 0)
633 		{
634 			lbmname.Format ("%sScreenshot_%s_%04d%02d%02d_%02d%02d%02d.%s", fullname.GetChars(), gamename,
635 				tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
636 				tm->tm_hour, tm->tm_min, tm->tm_sec,
637 				extension);
638 		}
639 		else
640 		{
641 			lbmname.Format ("%sScreenshot_%s_%04d%02d%02d_%02d%02d%02d_%02d.%s", fullname.GetChars(), gamename,
642 				tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
643 				tm->tm_hour, tm->tm_min, tm->tm_sec,
644 				i, extension);
645 		}
646 
647 		if (!FileExists (lbmname.GetChars()))
648 		{
649 			fullname = lbmname;
650 			return true;		// file doesn't exist
651 		}
652 	}
653 	return false;
654 }
655 
M_ScreenShot(const char * filename)656 void M_ScreenShot (const char *filename)
657 {
658 	FILE *file;
659 	FString autoname;
660 	bool writepcx = (stricmp (screenshot_type, "pcx") == 0);	// PNG is the default
661 
662 	// find a file name to save it to
663 	if (filename == NULL || filename[0] == '\0')
664 	{
665 		size_t dirlen;
666 		autoname = Args->CheckValue("-shotdir");
667 		if (autoname.IsEmpty())
668 		{
669 			autoname = screenshot_dir;
670 		}
671 		dirlen = autoname.Len();
672 		if (dirlen == 0)
673 		{
674 			autoname = M_GetScreenshotsPath();
675 			dirlen = autoname.Len();
676 		}
677 		if (dirlen > 0)
678 		{
679 			if (autoname[dirlen-1] != '/' && autoname[dirlen-1] != '\\')
680 			{
681 				autoname += '/';
682 			}
683 		}
684 		autoname = NicePath(autoname);
685 		CreatePath(autoname);
686 		if (!FindFreeName (autoname, writepcx ? "pcx" : "png"))
687 		{
688 			Printf ("M_ScreenShot: Delete some screenshots\n");
689 			return;
690 		}
691 	}
692 	else
693 	{
694 		autoname = filename;
695 		DefaultExtension (autoname, writepcx ? ".pcx" : ".png");
696 	}
697 
698 	// save the screenshot
699 	const BYTE *buffer;
700 	int pitch;
701 	ESSType color_type;
702 
703 	screen->GetScreenshotBuffer(buffer, pitch, color_type);
704 	if (buffer != NULL)
705 	{
706 		PalEntry palette[256];
707 
708 		if (color_type == SS_PAL)
709 		{
710 			screen->GetFlashedPalette(palette);
711 		}
712 		file = fopen (autoname, "wb");
713 		if (file == NULL)
714 		{
715 			Printf ("Could not open %s\n", autoname.GetChars());
716 			screen->ReleaseScreenshotBuffer();
717 			return;
718 		}
719 		if (writepcx)
720 		{
721 			WritePCXfile(file, buffer, palette, color_type,
722 				screen->GetWidth(), screen->GetHeight(), pitch);
723 		}
724 		else
725 		{
726 			WritePNGfile(file, buffer, palette, color_type,
727 				screen->GetWidth(), screen->GetHeight(), pitch);
728 		}
729 		fclose(file);
730 		screen->ReleaseScreenshotBuffer();
731 
732 		if (!screenshot_quiet)
733 		{
734 			int slash = -1;
735 			if (!longsavemessages) slash = autoname.LastIndexOfAny(":/\\");
736 			Printf ("Captured %s\n", autoname.GetChars()+slash+1);
737 		}
738 	}
739 	else
740 	{
741 		if (!screenshot_quiet)
742 		{
743 			Printf ("Could not create screenshot.\n");
744 		}
745 	}
746 }
747 
CCMD(screenshot)748 CCMD (screenshot)
749 {
750 	if (argv.argc() == 1)
751 		G_ScreenShot (NULL);
752 	else
753 		G_ScreenShot (argv[1]);
754 }
755 
756 //
757 // M_ZlibError
758 //
M_ZLibError(int zerr)759 FString M_ZLibError(int zerr)
760 {
761 	if (zerr >= 0)
762 	{
763 		return "OK";
764 	}
765 	else if (zerr < -6)
766 	{
767 		FString out;
768 		out.Format("%d", zerr);
769 		return out;
770 	}
771 	else
772 	{
773 		static const char *errs[6] =
774 		{
775 			"Errno",
776 			"Stream Error",
777 			"Data Error",
778 			"Memory Error",
779 			"Buffer Error",
780 			"Version Error"
781 		};
782 		return errs[-zerr - 1];
783 	}
784 }
785