mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-17 01:32:19 +00:00
0645053431
- If the current user does not have write permissions for the directory zdoom.exe is located in, use standard folder paths located in their home directory instead. This is a common scenario when people put ZDoom into Program Files. (Ironically, zdoom.ini used to be in AppData, buth then people complained when it wasn't in the same directory as zdoom.exe, so it got turned into zdoom-<user>.ini so at least it could retain some multi-user support. I'm not sure when the AppData support was removed, though, since it should have still been kept around for migrating configs to the new name.)
758 lines
15 KiB
C++
758 lines
15 KiB
C++
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id:$
|
|
//
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
//
|
|
// This source is available for distribution and/or modification
|
|
// only under the terms of the DOOM Source Code License as
|
|
// published by id Software. All rights reserved.
|
|
//
|
|
// The source is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
|
|
// for more details.
|
|
//
|
|
//
|
|
// $Log:$
|
|
//
|
|
// DESCRIPTION:
|
|
// Default Config File.
|
|
// Screenshots.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
#include "doomtype.h"
|
|
#include "version.h"
|
|
|
|
#if defined(_WIN32)
|
|
#include <io.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include "m_swap.h"
|
|
#include "m_argv.h"
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "c_cvars.h"
|
|
#include "c_dispatch.h"
|
|
#include "c_bind.h"
|
|
|
|
#include "i_system.h"
|
|
#include "i_video.h"
|
|
#include "v_video.h"
|
|
#include "r_defs.h"
|
|
|
|
#include "hu_stuff.h"
|
|
|
|
// State.
|
|
#include "doomstat.h"
|
|
|
|
// Data.
|
|
#include "m_misc.h"
|
|
#include "m_png.h"
|
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "g_game.h"
|
|
#include "gi.h"
|
|
|
|
#include "gameconfigfile.h"
|
|
|
|
FGameConfigFile *GameConfig;
|
|
|
|
CVAR(Bool, screenshot_quiet, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
|
|
CVAR(String, screenshot_type, "png", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
|
|
CVAR(String, screenshot_dir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
|
|
EXTERN_CVAR(Bool, longsavemessages);
|
|
|
|
static long ParseCommandLine (const char *args, int *argc, char **argv);
|
|
|
|
//
|
|
// M_WriteFile
|
|
//
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
bool M_WriteFile (char const *name, void *source, int length)
|
|
{
|
|
int handle;
|
|
int count;
|
|
|
|
handle = open ( name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
|
|
|
|
if (handle == -1)
|
|
return false;
|
|
|
|
count = write (handle, source, length);
|
|
close (handle);
|
|
|
|
if (count < length)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// M_ReadFile
|
|
//
|
|
int M_ReadFile (char const *name, BYTE **buffer)
|
|
{
|
|
int handle, count, length;
|
|
struct stat fileinfo;
|
|
BYTE *buf;
|
|
|
|
handle = open (name, O_RDONLY | O_BINARY, 0666);
|
|
if (handle == -1)
|
|
I_Error ("Couldn't read file %s", name);
|
|
if (fstat (handle,&fileinfo) == -1)
|
|
I_Error ("Couldn't read file %s", name);
|
|
length = fileinfo.st_size;
|
|
buf = new BYTE[length];
|
|
count = read (handle, buf, length);
|
|
close (handle);
|
|
|
|
if (count < length)
|
|
I_Error ("Couldn't read file %s", name);
|
|
|
|
*buffer = buf;
|
|
return length;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// PROC M_FindResponseFile
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void M_FindResponseFile (void)
|
|
{
|
|
const int limit = 100; // avoid infinite recursion
|
|
int added_stuff = 0;
|
|
int i = 1;
|
|
|
|
while (i < Args->NumArgs())
|
|
{
|
|
if (Args->GetArg(i)[0] != '@')
|
|
{
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
char **argv;
|
|
char *file = NULL;
|
|
int argc = 0;
|
|
FILE *handle;
|
|
int size;
|
|
long argsize = 0;
|
|
int index;
|
|
|
|
// Any more response files after the limit will be removed from the
|
|
// command line.
|
|
if (added_stuff < limit)
|
|
{
|
|
// READ THE RESPONSE FILE INTO MEMORY
|
|
handle = fopen (Args->GetArg(i) + 1,"rb");
|
|
if (!handle)
|
|
{ // [RH] Make this a warning, not an error.
|
|
Printf ("No such response file (%s)!\n", Args->GetArg(i) + 1);
|
|
}
|
|
else
|
|
{
|
|
Printf ("Found response file %s!\n", Args->GetArg(i) + 1);
|
|
fseek (handle, 0, SEEK_END);
|
|
size = ftell (handle);
|
|
fseek (handle, 0, SEEK_SET);
|
|
file = new char[size+1];
|
|
fread (file, size, 1, handle);
|
|
file[size] = 0;
|
|
fclose (handle);
|
|
|
|
argsize = ParseCommandLine (file, &argc, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Printf ("Ignored response file %s.\n", Args->GetArg(i) + 1);
|
|
}
|
|
|
|
if (argc != 0)
|
|
{
|
|
argv = (char **)M_Malloc (argc*sizeof(char *) + argsize);
|
|
argv[0] = (char *)argv + argc*sizeof(char *);
|
|
ParseCommandLine (file, NULL, argv);
|
|
|
|
// Create a new argument vector
|
|
DArgs *newargs = new DArgs;
|
|
|
|
// Copy parameters before response file.
|
|
for (index = 0; index < i; ++index)
|
|
newargs->AppendArg(Args->GetArg(index));
|
|
|
|
// Copy parameters from response file.
|
|
for (index = 0; index < argc; ++index)
|
|
newargs->AppendArg(argv[index]);
|
|
|
|
// Copy parameters after response file.
|
|
for (index = i + 1; index < Args->NumArgs(); ++index)
|
|
newargs->AppendArg(Args->GetArg(index));
|
|
|
|
// Use the new argument vector as the global Args object.
|
|
Args = newargs;
|
|
if (++added_stuff == limit)
|
|
{
|
|
Printf("Response file limit of %d hit.\n", limit);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove the response file from the Args object
|
|
Args->RemoveArg(i);
|
|
}
|
|
if (file != NULL)
|
|
{
|
|
delete[] file;
|
|
}
|
|
}
|
|
}
|
|
if (added_stuff > 0)
|
|
{
|
|
// DISPLAY ARGS
|
|
Printf ("Added %d response file%s, now have %d command-line args:\n",
|
|
added_stuff, added_stuff > 1 ? "s" : "", Args->NumArgs ());
|
|
for (int k = 1; k < Args->NumArgs (); k++)
|
|
Printf ("%s\n", Args->GetArg (k));
|
|
}
|
|
}
|
|
|
|
// ParseCommandLine
|
|
//
|
|
// This is just like the version in c_dispatch.cpp, except it does not
|
|
// do cvar expansion.
|
|
|
|
static long ParseCommandLine (const char *args, int *argc, char **argv)
|
|
{
|
|
int count;
|
|
char *buffplace;
|
|
|
|
count = 0;
|
|
buffplace = NULL;
|
|
if (argv != NULL)
|
|
{
|
|
buffplace = argv[0];
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
while (*args <= ' ' && *args)
|
|
{ // skip white space
|
|
args++;
|
|
}
|
|
if (*args == 0)
|
|
{
|
|
break;
|
|
}
|
|
else if (*args == '\"')
|
|
{ // read quoted string
|
|
char stuff;
|
|
if (argv != NULL)
|
|
{
|
|
argv[count] = buffplace;
|
|
}
|
|
count++;
|
|
args++;
|
|
do
|
|
{
|
|
stuff = *args++;
|
|
if (stuff == '\\' && *args == '\"')
|
|
{
|
|
stuff = '\"', args++;
|
|
}
|
|
else if (stuff == '\"')
|
|
{
|
|
stuff = 0;
|
|
}
|
|
else if (stuff == 0)
|
|
{
|
|
args--;
|
|
}
|
|
if (argv != NULL)
|
|
{
|
|
*buffplace = stuff;
|
|
}
|
|
buffplace++;
|
|
} while (stuff);
|
|
}
|
|
else
|
|
{ // read unquoted string
|
|
const char *start = args++, *end;
|
|
|
|
while (*args && *args > ' ' && *args != '\"')
|
|
args++;
|
|
end = args;
|
|
if (argv != NULL)
|
|
{
|
|
argv[count] = buffplace;
|
|
while (start < end)
|
|
*buffplace++ = *start++;
|
|
*buffplace++ = 0;
|
|
}
|
|
else
|
|
{
|
|
buffplace += end - start + 1;
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
if (argc != NULL)
|
|
{
|
|
*argc = count;
|
|
}
|
|
return (long)(buffplace - (char *)0);
|
|
}
|
|
|
|
|
|
//
|
|
// M_SaveDefaults
|
|
//
|
|
|
|
bool M_SaveDefaults (const char *filename)
|
|
{
|
|
FString oldpath;
|
|
bool success;
|
|
|
|
if (filename != NULL)
|
|
{
|
|
oldpath = GameConfig->GetPathName();
|
|
GameConfig->ChangePathName (filename);
|
|
}
|
|
GameConfig->ArchiveGlobalData ();
|
|
if (gameinfo.ConfigName.IsNotEmpty())
|
|
{
|
|
GameConfig->ArchiveGameData (gameinfo.ConfigName);
|
|
}
|
|
success = GameConfig->WriteConfigFile ();
|
|
if (filename != NULL)
|
|
{
|
|
GameConfig->ChangePathName (filename);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void M_SaveDefaultsFinal ()
|
|
{
|
|
while (!M_SaveDefaults (NULL) && I_WriteIniFailed ())
|
|
{
|
|
/* Loop until the config saves or I_WriteIniFailed() returns false */
|
|
}
|
|
delete GameConfig;
|
|
GameConfig = NULL;
|
|
}
|
|
|
|
CCMD (writeini)
|
|
{
|
|
const char *filename = (argv.argc() == 1) ? NULL : argv[1];
|
|
if (!M_SaveDefaults (filename))
|
|
{
|
|
Printf ("Writing config failed: %s\n", strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
Printf ("Config saved.\n");
|
|
}
|
|
}
|
|
|
|
//
|
|
// M_LoadDefaults
|
|
//
|
|
|
|
void M_LoadDefaults ()
|
|
{
|
|
GameConfig = new FGameConfigFile;
|
|
GameConfig->DoGlobalSetup ();
|
|
atterm (M_SaveDefaultsFinal);
|
|
}
|
|
|
|
|
|
//
|
|
// SCREEN SHOTS
|
|
//
|
|
|
|
|
|
struct pcx_t
|
|
{
|
|
char manufacturer;
|
|
char version;
|
|
char encoding;
|
|
char bits_per_pixel;
|
|
|
|
unsigned short xmin;
|
|
unsigned short ymin;
|
|
unsigned short xmax;
|
|
unsigned short ymax;
|
|
|
|
unsigned short hdpi;
|
|
unsigned short vdpi;
|
|
|
|
unsigned char palette[48];
|
|
|
|
char reserved;
|
|
char color_planes;
|
|
unsigned short bytes_per_line;
|
|
unsigned short palette_type;
|
|
|
|
char filler[58];
|
|
};
|
|
|
|
|
|
//
|
|
// WritePCXfile
|
|
//
|
|
void WritePCXfile (FILE *file, const BYTE *buffer, const PalEntry *palette,
|
|
ESSType color_type, int width, int height, int pitch)
|
|
{
|
|
BYTE temprow[MAXWIDTH * 3];
|
|
const BYTE *data;
|
|
int x, y;
|
|
int runlen;
|
|
int bytes_per_row_minus_one;
|
|
BYTE color;
|
|
pcx_t pcx;
|
|
|
|
pcx.manufacturer = 10; // PCX id
|
|
pcx.version = 5; // 256 (or more) colors
|
|
pcx.encoding = 1;
|
|
pcx.bits_per_pixel = 8; // 256 (or more) colors
|
|
pcx.xmin = 0;
|
|
pcx.ymin = 0;
|
|
pcx.xmax = LittleShort((unsigned short)(width-1));
|
|
pcx.ymax = LittleShort((unsigned short)(height-1));
|
|
pcx.hdpi = LittleShort((unsigned short)75);
|
|
pcx.vdpi = LittleShort((unsigned short)75);
|
|
memset (pcx.palette, 0, sizeof(pcx.palette));
|
|
pcx.reserved = 0;
|
|
pcx.color_planes = (color_type == SS_PAL) ? 1 : 3; // chunky image
|
|
pcx.bytes_per_line = width + (width & 1);
|
|
pcx.palette_type = 1; // not a grey scale
|
|
memset (pcx.filler, 0, sizeof(pcx.filler));
|
|
|
|
fwrite (&pcx, 128, 1, file);
|
|
|
|
bytes_per_row_minus_one = ((color_type == SS_PAL) ? width : width * 3) - 1;
|
|
|
|
// pack the image
|
|
for (y = height; y > 0; y--)
|
|
{
|
|
switch (color_type)
|
|
{
|
|
case SS_PAL:
|
|
data = buffer;
|
|
break;
|
|
|
|
case SS_RGB:
|
|
// Unpack RGB into separate planes.
|
|
for (int i = 0; i < width; ++i)
|
|
{
|
|
temprow[i ] = buffer[i*3];
|
|
temprow[i + width ] = buffer[i*3 + 1];
|
|
temprow[i + width * 2] = buffer[i*3 + 2];
|
|
}
|
|
data = temprow;
|
|
break;
|
|
|
|
case SS_BGRA:
|
|
// Unpack RGB into separate planes, discarding A.
|
|
for (int i = 0; i < width; ++i)
|
|
{
|
|
temprow[i ] = buffer[i*4 + 2];
|
|
temprow[i + width ] = buffer[i*4 + 1];
|
|
temprow[i + width * 2] = buffer[i*4];
|
|
}
|
|
data = temprow;
|
|
break;
|
|
|
|
default:
|
|
// Should never happen.
|
|
return;
|
|
}
|
|
buffer += pitch;
|
|
|
|
color = *data++;
|
|
runlen = 1;
|
|
|
|
for (x = bytes_per_row_minus_one; x > 0; x--)
|
|
{
|
|
if (*data == color)
|
|
{
|
|
runlen++;
|
|
}
|
|
else
|
|
{
|
|
if (runlen > 1 || color >= 0xc0)
|
|
{
|
|
while (runlen > 63)
|
|
{
|
|
putc (0xff, file);
|
|
putc (color, file);
|
|
runlen -= 63;
|
|
}
|
|
if (runlen > 0)
|
|
{
|
|
putc (0xc0 + runlen, file);
|
|
}
|
|
}
|
|
if (runlen > 0)
|
|
{
|
|
putc (color, file);
|
|
}
|
|
runlen = 1;
|
|
color = *data;
|
|
}
|
|
data++;
|
|
}
|
|
|
|
if (runlen > 1 || color >= 0xc0)
|
|
{
|
|
while (runlen > 63)
|
|
{
|
|
putc (0xff, file);
|
|
putc (color, file);
|
|
runlen -= 63;
|
|
}
|
|
if (runlen > 0)
|
|
{
|
|
putc (0xc0 + runlen, file);
|
|
}
|
|
}
|
|
if (runlen > 0)
|
|
{
|
|
putc (color, file);
|
|
}
|
|
|
|
if (width & 1)
|
|
putc (0, file);
|
|
}
|
|
|
|
// write the palette
|
|
if (color_type == SS_PAL)
|
|
{
|
|
putc (12, file); // palette ID byte
|
|
for (x = 0; x < 256; x++, palette++)
|
|
{
|
|
putc (palette->r, file);
|
|
putc (palette->g, file);
|
|
putc (palette->b, file);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// WritePNGfile
|
|
//
|
|
void WritePNGfile (FILE *file, const BYTE *buffer, const PalEntry *palette,
|
|
ESSType color_type, int width, int height, int pitch)
|
|
{
|
|
char software[100];
|
|
mysnprintf(software, countof(software), GAMENAME " %s", GetVersionString());
|
|
if (!M_CreatePNG (file, buffer, palette, color_type, width, height, pitch) ||
|
|
!M_AppendPNGText (file, "Software", software) ||
|
|
!M_FinishPNG (file))
|
|
{
|
|
Printf ("Could not create screenshot.\n");
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// M_ScreenShot
|
|
//
|
|
static bool FindFreeName (FString &fullname, const char *extension)
|
|
{
|
|
FString lbmname;
|
|
int i;
|
|
|
|
for (i = 0; i <= 9999; i++)
|
|
{
|
|
const char *gamename = gameinfo.ConfigName;
|
|
|
|
time_t now;
|
|
tm *tm;
|
|
|
|
time(&now);
|
|
tm = localtime(&now);
|
|
|
|
if (tm == NULL)
|
|
{
|
|
lbmname.Format ("%sScreenshot_%s_%04d.%s", fullname.GetChars(), gamename, i, extension);
|
|
}
|
|
else if (i == 0)
|
|
{
|
|
lbmname.Format ("%sScreenshot_%s_%04d%02d%02d_%02d%02d%02d.%s", fullname.GetChars(), gamename,
|
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec,
|
|
extension);
|
|
}
|
|
else
|
|
{
|
|
lbmname.Format ("%sScreenshot_%s_%04d%02d%02d_%02d%02d%02d_%02d.%s", fullname.GetChars(), gamename,
|
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec,
|
|
i, extension);
|
|
}
|
|
|
|
if (!FileExists (lbmname.GetChars()))
|
|
{
|
|
fullname = lbmname;
|
|
return true; // file doesn't exist
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void M_ScreenShot (const char *filename)
|
|
{
|
|
FILE *file;
|
|
FString autoname;
|
|
bool writepcx = (stricmp (screenshot_type, "pcx") == 0); // PNG is the default
|
|
|
|
// find a file name to save it to
|
|
if (filename == NULL || filename[0] == '\0')
|
|
{
|
|
size_t dirlen;
|
|
autoname = Args->CheckValue("-shotdir");
|
|
if (autoname.IsEmpty())
|
|
{
|
|
autoname = screenshot_dir;
|
|
}
|
|
dirlen = autoname.Len();
|
|
if (dirlen == 0)
|
|
{
|
|
autoname = M_GetScreenshotsPath();
|
|
dirlen = autoname.Len();
|
|
}
|
|
if (dirlen > 0)
|
|
{
|
|
if (autoname[dirlen-1] != '/' && autoname[dirlen-1] != '\\')
|
|
{
|
|
autoname += '/';
|
|
}
|
|
}
|
|
autoname = NicePath(autoname);
|
|
CreatePath(autoname);
|
|
if (!FindFreeName (autoname, writepcx ? "pcx" : "png"))
|
|
{
|
|
Printf ("M_ScreenShot: Delete some screenshots\n");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
autoname = filename;
|
|
DefaultExtension (autoname, writepcx ? ".pcx" : ".png");
|
|
}
|
|
|
|
// save the screenshot
|
|
const BYTE *buffer;
|
|
int pitch;
|
|
ESSType color_type;
|
|
|
|
screen->GetScreenshotBuffer(buffer, pitch, color_type);
|
|
if (buffer != NULL)
|
|
{
|
|
PalEntry palette[256];
|
|
|
|
if (color_type == SS_PAL)
|
|
{
|
|
screen->GetFlashedPalette(palette);
|
|
}
|
|
file = fopen (autoname, "wb");
|
|
if (file == NULL)
|
|
{
|
|
Printf ("Could not open %s\n", autoname.GetChars());
|
|
screen->ReleaseScreenshotBuffer();
|
|
return;
|
|
}
|
|
if (writepcx)
|
|
{
|
|
WritePCXfile(file, buffer, palette, color_type,
|
|
screen->GetWidth(), screen->GetHeight(), pitch);
|
|
}
|
|
else
|
|
{
|
|
WritePNGfile(file, buffer, palette, color_type,
|
|
screen->GetWidth(), screen->GetHeight(), pitch);
|
|
}
|
|
fclose(file);
|
|
screen->ReleaseScreenshotBuffer();
|
|
|
|
if (!screenshot_quiet)
|
|
{
|
|
int slash = -1;
|
|
if (!longsavemessages) slash = autoname.LastIndexOfAny(":/\\");
|
|
Printf ("Captured %s\n", autoname.GetChars()+slash+1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!screenshot_quiet)
|
|
{
|
|
Printf ("Could not create screenshot.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
CCMD (screenshot)
|
|
{
|
|
if (argv.argc() == 1)
|
|
G_ScreenShot (NULL);
|
|
else
|
|
G_ScreenShot (argv[1]);
|
|
}
|
|
|
|
//
|
|
// M_ZlibError
|
|
//
|
|
FString M_ZLibError(int zerr)
|
|
{
|
|
if (zerr >= 0)
|
|
{
|
|
return "OK";
|
|
}
|
|
else if (zerr < -6)
|
|
{
|
|
FString out;
|
|
out.Format("%d", zerr);
|
|
return out;
|
|
}
|
|
else
|
|
{
|
|
static const char *errs[6] =
|
|
{
|
|
"Errno",
|
|
"Stream Error",
|
|
"Data Error",
|
|
"Memory Error",
|
|
"Buffer Error",
|
|
"Version Error"
|
|
};
|
|
return errs[-zerr - 1];
|
|
}
|
|
}
|