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