ericw-tools/common/cmdlib.cc

549 lines
12 KiB
C++

/* Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See file, 'COPYING', for details.
*/
#include <common/cmdlib.hh>
#include <common/log.hh>
#include <common/threads.hh>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef WIN32
#include <direct.h>
#include <windows.h>
#endif
#ifdef LINUX
#include <sys/time.h>
#include <unistd.h>
#include <cstring>
#endif
#include <cstdint>
#include <string>
constexpr char PATHSEPERATOR = '/';
/* set these before calling CheckParm */
int myargc;
char **myargv;
char com_token[1024];
bool com_eof;
/*
* =================
* Error
* For abnormal program terminations
* =================
*/
[[noreturn]] void Error(const char *error)
{
/* Using lockless prints so we can error out while holding the lock */
InterruptThreadProgress__();
LogPrintLocked("************ ERROR ************\n{}\n", error);
exit(1);
}
void // mxd
string_replaceall(std::string &str, const std::string &from, const std::string &to)
{
if (from.empty())
return;
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
bool // mxd
string_iequals(const std::string &a, const std::string &b)
{
size_t sz = a.size();
if (b.size() != sz)
return false;
for (size_t i = 0; i < sz; ++i)
if (tolower(a[i]) != tolower(b[i]))
return false;
return true;
}
std::filesystem::path qdir, gamedir, basedir;
/** It's possible to compile quake 1/hexen 2 maps without a qdir */
inline void ClearQdir()
{
qdir.clear();
gamedir.clear();
basedir.clear();
}
constexpr const char *MAPS_FOLDER = "maps";
// mxd. Expects the path to contain "maps" folder
void SetQdirFromPath(const std::string &basedirname, std::filesystem::path path)
{
// expand canonicals, and fetch parent of source file
// (maps/source.map -> C:/Quake/ID1/maps/)
path = std::filesystem::canonical(path).parent_path();
// make sure we're maps/
if (path.filename() != "maps") {
FLogPrint("WARNING: '{}' is not directly inside '{}'\n", path, MAPS_FOLDER);
return;
}
// set gamedir (it should be "above" the source)
// (C:/Quake/ID1/maps/ -> C:/Quake/ID1/)
gamedir = path.parent_path();
LogPrint("INFO: gamedir: '{}'\n", gamedir);
// set qdir (it should be above gamedir)
// (C:/Quake/ID1/ -> C:/Quake/)
qdir = gamedir.parent_path();
LogPrint("INFO: qdir: '{}'\n", qdir);
// Set base dir and make sure it exists
basedir = qdir / basedirname;
if (!std::filesystem::exists(basedir)) {
FLogPrint("WARNING: failed to find '{}' in '{}'\n", basedirname, qdir);
ClearQdir();
return;
}
}
int Q_strncasecmp(const char *s1, const char *s2, int n)
{
int c1, c2;
while (1) {
c1 = *s1++;
c2 = *s2++;
if (!n--)
return 0; /* strings are equal until end point */
if (c1 != c2) {
if (c1 >= 'a' && c1 <= 'z')
c1 -= ('a' - 'A');
if (c2 >= 'a' && c2 <= 'z')
c2 -= ('a' - 'A');
if (c1 != c2)
return c1 < c2 ? -1 : 1; /* strings not equal */
}
if (!c1)
return 0; /* strings are equal */
}
return 0;
}
int Q_strcasecmp(const char *s1, const char *s2)
{
return Q_strncasecmp(s1, s2, 99999);
}
char *Q_strupr(char *start)
{
char *in;
in = start;
while (*in) {
*in = toupper(*in);
in++;
}
return start;
}
char *Q_strlower(char *start)
{
char *in;
in = start;
while (*in) {
*in = tolower(*in);
in++;
}
return start;
}
/* ============================================================================
* MISC FUNCTIONS
* ============================================================================
*/
/*
* =================
* CheckParm
* Checks for the given parameter in the program's command line arguments
* Returns the argument number (1 to argc-1) or 0 if not present
* =================
*/
int CheckParm(const char *check)
{
int i;
for (i = 1; i < myargc; i++) {
if (!Q_strcasecmp(check, myargv[i]))
return i;
}
return 0;
}
qfile_t SafeOpenWrite(const std::filesystem::path &filename)
{
FILE *f;
#ifdef _WIN32
f = _wfopen(filename.c_str(), L"wb");
#else
f = fopen(filename.string().c_str(), "wb");
#endif
if (!f)
FError("Error opening {}: {}", filename, strerror(errno));
return { f, fclose };
}
qfile_t SafeOpenRead(const std::filesystem::path &filename, bool must_exist)
{
FILE *f;
#ifdef _WIN32
f = _wfopen(filename.c_str(), L"rb");
#else
f = fopen(filename.string().c_str(), "rb");
#endif
if (!f)
{
if (must_exist)
FError("Error opening {}: {}", filename, strerror(errno));
return { nullptr, nullptr };
}
return { f, fclose };
}
size_t SafeRead(const qfile_t &f, void *buffer, size_t count)
{
if (fread(buffer, 1, count, f.get()) != (size_t)count)
FError("File read failure");
return count;
}
size_t SafeWrite(const qfile_t &f, const void *buffer, size_t count)
{
if (fwrite(buffer, 1, count, f.get()) != (size_t)count)
FError("File write failure");
return count;
}
void SafeSeek(const qfile_t &f, long offset, int32_t origin)
{
fseek(f.get(), offset, origin);
}
long SafeTell(const qfile_t &f)
{
return ftell(f.get());
}
struct pakheader_t
{
char magic[4];
unsigned int tableofs;
unsigned int numfiles;
};
struct pakfile_t
{
char name[56];
unsigned int offset;
unsigned int length;
};
/*
* ==============
* LoadFilePak
* reads a file directly out of a pak, to make re-lighting friendlier
* writes to the filename, stripping the pak part of the name
* ==============
*/
long LoadFilePak(std::filesystem::path &filename, void *destptr)
{
// check if we have a .pak file in this path
for (auto p = filename.parent_path(); !p.empty() && p != p.root_path(); p = p.parent_path()) {
if (p.extension() == ".pak") {
qfile_t file = SafeOpenRead(p);
// false positive
if (!file)
continue;
// got one; calculate the relative remaining path
auto innerfile = filename.lexically_relative(p);
uint8_t **bufferptr = static_cast<uint8_t **>(destptr);
pakheader_t header;
long length = -1;
SafeRead(file, &header, sizeof(header));
header.numfiles = LittleLong(header.numfiles) / sizeof(pakfile_t);
header.tableofs = LittleLong(header.tableofs);
if (!strncmp(header.magic, "PACK", 4)) {
pakfile_t *files = new pakfile_t[header.numfiles];
SafeSeek(file, header.tableofs, SEEK_SET);
SafeRead(file, files, header.numfiles * sizeof(*files));
for (uint32_t i = 0; i < header.numfiles; i++) {
if (innerfile == files[i].name) {
SafeSeek(file, files[i].offset, SEEK_SET);
*bufferptr = new uint8_t[files[i].length + 1];
SafeRead(file, *bufferptr, files[i].length);
length = files[i].length;
break;
}
}
delete[] files;
}
if (length < 0)
FError("Unable to find '{}' inside '{}'", innerfile, filename);
filename = innerfile;
return length;
}
}
// not in a pak, so load it normally
return LoadFile(filename, destptr);
}
/*
* ===========
* filelength
* ===========
*/
static long Sys_FileLength(const qfile_t &f)
{
long pos = ftell(f.get());
fseek(f.get(), 0, SEEK_END);
long end = ftell(f.get());
fseek(f.get(), pos, SEEK_SET);
return end;
}
/*
* ==============
* LoadFile
* ==============
*/
long LoadFile(const std::filesystem::path &filename, void *destptr)
{
uint8_t **bufferptr = static_cast<uint8_t **>(destptr);
qfile_t file = SafeOpenRead(filename);
long length = Sys_FileLength(file);
uint8_t * buffer = *bufferptr = new uint8_t[length + 1];
if (!buffer)
FError("allocation of {} bytes failed.", length);
SafeRead(file, buffer, length);
buffer[length] = 0;
return length;
}
/*
* ==============
* ParseNum / ParseHex
* ==============
*/
int ParseHex(char *hex)
{
char *str;
int num;
num = 0;
str = hex;
while (*str) {
num <<= 4;
if (*str >= '0' && *str <= '9')
num += *str - '0';
else if (*str >= 'a' && *str <= 'f')
num += 10 + *str - 'a';
else if (*str >= 'A' && *str <= 'F')
num += 10 + *str - 'A';
else
FError("Bad hex number: {}", hex);
str++;
}
return num;
}
int ParseNum(char *str)
{
if (str[0] == '$')
return ParseHex(str + 1);
if (str[0] == '0' && str[1] == 'x')
return ParseHex(str + 2);
return atol(str);
}
/*
* ============================================================================
* BYTE ORDER FUNCTIONS
* ============================================================================
*/
#ifdef _SGI_SOURCE
#define __BIG_ENDIAN__
#endif
#ifdef __BIG_ENDIAN__
short LittleShort(short l)
{
uint8_t b1, b2;
b1 = l & 255;
b2 = (l >> 8) & 255;
return (b1 << 8) + b2;
}
short BigShort(short l)
{
return l;
}
int LittleLong(int l)
{
uint8_t b1, b2, b3, b4;
b1 = l & 255;
b2 = (l >> 8) & 255;
b3 = (l >> 16) & 255;
b4 = (l >> 24) & 255;
return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
}
int BigLong(int l)
{
return l;
}
float LittleFloat(float l)
{
union
{
uint8_t b[4];
float f;
} in, out;
in.f = l;
out.b[0] = in.b[3];
out.b[1] = in.b[2];
out.b[2] = in.b[1];
out.b[3] = in.b[0];
return out.f;
}
float BigFloat(float l)
{
return l;
}
#else /* must be little endian */
short BigShort(short l)
{
uint8_t b1, b2;
b1 = l & 255;
b2 = (l >> 8) & 255;
return (b1 << 8) + b2;
}
short LittleShort(short l)
{
return l;
}
int BigLong(int l)
{
uint8_t b1, b2, b3, b4;
b1 = l & 255;
b2 = (l >> 8) & 255;
b3 = (l >> 16) & 255;
b4 = (l >> 24) & 255;
return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
}
int LittleLong(int l)
{
return l;
}
float BigFloat(float l)
{
union
{
uint8_t b[4];
float f;
} in, out;
in.f = l;
out.b[0] = in.b[3];
out.b[1] = in.b[2];
out.b[2] = in.b[1];
out.b[3] = in.b[0];
return out.f;
}
float LittleFloat(float l)
{
return l;
}
#endif