1
0
mirror of https://git.zap.org.au/git/trader.git synced 2024-11-03 17:27:29 -05:00
trader/src/utils.c
2024-01-02 12:33:37 +11:00

1431 lines
42 KiB
C

/************************************************************************
* *
* Star Traders: A Game of Interstellar Trading *
* Copyright (C) 1990-2024, John Zaitseff *
* *
************************************************************************/
/*
Author: John Zaitseff <J.Zaitseff@zap.org.au>
$Id$
This file, utils.c, contains the implementation of various utility
functions used in Star Traders.
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 3 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, see https://www.gnu.org/licenses/.
*/
#include "trader.h"
/************************************************************************
* Global variable definitions *
************************************************************************/
const char *program_name = NULL; // Canonical program name
// Global copy, suitably modified, of localeconv() information
struct lconv lconvinfo;
// localeconv() information, converted to wide strings
wchar_t *decimal_point; // Locale's radix character
wchar_t *thousands_sep; // Locale's thousands separator
wchar_t *currency_symbol; // Locale's currency symbol
wchar_t *mon_decimal_point; // Locale's monetary radix character
wchar_t *mon_thousands_sep; // Locale's monetary thousands separator
/************************************************************************
* Module-specific constants and macros *
************************************************************************/
#define APPIMAGE_NAME "AppRun.wrapped"
#define DIRSEP "/" // Directory separator
#define CURDIR "." // Current directory
#define HIDDEN_PATH "." // Hidden file start char
#define XDG_DATA_DEFAULT ".local" DIRSEP "share"
#define GAME_FILENAME_PROTO "game%d"
#define GAME_FILENAME_BUFSIZE 16
// Values used to override the standard POSIX locale
#define MOD_POSIX_DECIMAL_POINT "."
#define MOD_POSIX_THOUSANDS_SEP ""
#define MOD_POSIX_GROUPING ""
#define MOD_POSIX_INT_CURR_SYMBOL ""
#define MOD_POSIX_CURRENCY_SYMBOL "$" // Standard: ""
#define MOD_POSIX_MON_DECIMAL_POINT "." // Standard: ""
#define MOD_POSIX_MON_THOUSANDS_SEP ""
#define MOD_POSIX_MON_GROUPING ""
#define MOD_POSIX_POSITIVE_SIGN ""
#define MOD_POSIX_NEGATIVE_SIGN "-" // Standard: ""
#define MOD_POSIX_INT_FRAC_DIGITS 2 // Standard: CHAR_MAX
#define MOD_POSIX_FRAC_DIGITS 2 // Standard: CHAR_MAX
#define MOD_POSIX_P_CS_PRECEDES 1 // Standard: CHAR_MAX
#define MOD_POSIX_P_SEP_BY_SPACE 0 // Standard: CHAR_MAX
#define MOD_POSIX_N_CS_PRECEDES 1 // Standard: CHAR_MAX
#define MOD_POSIX_N_SEP_BY_SPACE 0 // Standard: CHAR_MAX
#define MOD_POSIX_P_SIGN_POSN 1 // Standard: CHAR_MAX
#define MOD_POSIX_N_SIGN_POSN 1 // Standard: CHAR_MAX
static const struct lconv mod_posix_lconv =
{
.decimal_point = MOD_POSIX_DECIMAL_POINT,
.thousands_sep = MOD_POSIX_THOUSANDS_SEP,
.grouping = MOD_POSIX_GROUPING,
.int_curr_symbol = MOD_POSIX_INT_CURR_SYMBOL,
.currency_symbol = MOD_POSIX_CURRENCY_SYMBOL,
.mon_decimal_point = MOD_POSIX_MON_DECIMAL_POINT,
.mon_thousands_sep = MOD_POSIX_MON_THOUSANDS_SEP,
.mon_grouping = MOD_POSIX_MON_GROUPING,
.positive_sign = MOD_POSIX_POSITIVE_SIGN,
.negative_sign = MOD_POSIX_NEGATIVE_SIGN,
.int_frac_digits = MOD_POSIX_INT_FRAC_DIGITS,
.frac_digits = MOD_POSIX_FRAC_DIGITS,
.p_cs_precedes = MOD_POSIX_P_CS_PRECEDES,
.p_sep_by_space = MOD_POSIX_P_SEP_BY_SPACE,
.n_cs_precedes = MOD_POSIX_N_CS_PRECEDES,
.n_sep_by_space = MOD_POSIX_N_SEP_BY_SPACE,
.p_sign_posn = MOD_POSIX_P_SIGN_POSN,
.n_sign_posn = MOD_POSIX_N_SIGN_POSN
// ISO/IEC 9945-1:2008 (SUSv4) defines additional fields, but
// this program does not use them.
};
// Constants used for scrambling and unscrambling game data
#define SCRAMBLE_CRC_LEN 8 // Length of CRC in ASCII (excl NUL)
#define SCRAMBLE_CHKSUM_LEN 3 // For checksum, excluding NUL byte
#define SCRAMBLE_CRC_MASK 0xFFFFFFFF // Bits of CRC to keep
#define SCRAMBLE_CHKSUM_MASK 0x0FFF // Bits of checksum to keep
#define SCRAMBLE_PAD_CHAR '*'
#define SCRAMBLE_IGNORE_CHAR '~'
#define UNSCRAMBLE_INVALID (-1)
#define UNSCRAMBLE_IGNORE (-2)
#define UNSCRAMBLE_PAD_CHAR (-3)
static const char scramble_table[] =
"0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz-_";
#define _b(n) \
((n) == '0' ? 0 : (n) == '1' ? 1 : (n) == '2' ? 2 : (n) == '3' ? 3 : \
(n) == '4' ? 4 : (n) == '5' ? 5 : (n) == '6' ? 6 : (n) == '7' ? 7 : \
(n) == '8' ? 8 : (n) == '9' ? 9 : (n) == 'A' ? 10 : (n) == 'a' ? 11 : \
(n) == 'B' ? 12 : (n) == 'b' ? 13 : (n) == 'C' ? 14 : (n) == 'c' ? 15 : \
(n) == 'D' ? 16 : (n) == 'd' ? 17 : (n) == 'E' ? 18 : (n) == 'e' ? 19 : \
(n) == 'F' ? 20 : (n) == 'f' ? 21 : (n) == 'G' ? 22 : (n) == 'g' ? 23 : \
(n) == 'H' ? 24 : (n) == 'h' ? 25 : (n) == 'I' ? 26 : (n) == 'i' ? 27 : \
(n) == 'J' ? 28 : (n) == 'j' ? 29 : (n) == 'K' ? 30 : (n) == 'k' ? 31 : \
(n) == 'L' ? 32 : (n) == 'l' ? 33 : (n) == 'M' ? 34 : (n) == 'm' ? 35 : \
(n) == 'N' ? 36 : (n) == 'n' ? 37 : (n) == 'O' ? 38 : (n) == 'o' ? 39 : \
(n) == 'P' ? 40 : (n) == 'p' ? 41 : (n) == 'Q' ? 42 : (n) == 'q' ? 43 : \
(n) == 'R' ? 44 : (n) == 'r' ? 45 : (n) == 'S' ? 46 : (n) == 's' ? 47 : \
(n) == 'T' ? 48 : (n) == 't' ? 49 : (n) == 'U' ? 50 : (n) == 'u' ? 51 : \
(n) == 'V' ? 52 : (n) == 'v' ? 53 : (n) == 'W' ? 54 : (n) == 'w' ? 55 : \
(n) == 'X' ? 56 : (n) == 'x' ? 57 : (n) == 'Y' ? 58 : (n) == 'y' ? 59 : \
(n) == 'Z' ? 60 : (n) == 'z' ? 61 : (n) == '-' ? 62 : (n) == '_' ? 63 : \
(n) == ' ' ? UNSCRAMBLE_IGNORE : \
(n) == '\t' ? UNSCRAMBLE_IGNORE : \
(n) == '\n' ? UNSCRAMBLE_IGNORE : \
(n) == '\r' ? UNSCRAMBLE_IGNORE : \
(n) == SCRAMBLE_IGNORE_CHAR ? UNSCRAMBLE_IGNORE : \
(n) == SCRAMBLE_PAD_CHAR ? UNSCRAMBLE_PAD_CHAR : \
UNSCRAMBLE_INVALID)
static const signed char unscramble_table[] = {
_b(0), _b(1), _b(2), _b(3), _b(4), _b(5), _b(6), _b(7),
_b(8), _b(9), _b(10) , _b(11), _b(12), _b(13), _b(14), _b(15),
_b(16), _b(17), _b(18), _b(19), _b(20), _b(21), _b(22), _b(23),
_b(24), _b(25), _b(26), _b(27), _b(28), _b(29), _b(30), _b(31),
_b(32), _b(33), _b(34), _b(35), _b(36), _b(37), _b(38), _b(39),
_b(40), _b(41), _b(42), _b(43), _b(44), _b(45), _b(46), _b(47),
_b(48), _b(49), _b(50), _b(51), _b(52), _b(53), _b(54), _b(55),
_b(56), _b(57), _b(58), _b(59), _b(60), _b(61), _b(62), _b(63),
_b(64), _b(65), _b(66), _b(67), _b(68), _b(69), _b(70), _b(71),
_b(72), _b(73), _b(74), _b(75), _b(76), _b(77), _b(78), _b(79),
_b(80), _b(81), _b(82), _b(83), _b(84), _b(85), _b(86), _b(87),
_b(88), _b(89), _b(90), _b(91), _b(92), _b(93), _b(94), _b(95),
_b(96), _b(97), _b(98), _b(99), _b(100), _b(101), _b(102), _b(103),
_b(104), _b(105), _b(106), _b(107), _b(108), _b(109), _b(110), _b(111),
_b(112), _b(113), _b(114), _b(115), _b(116), _b(117), _b(118), _b(119),
_b(120), _b(121), _b(122), _b(123), _b(124), _b(125), _b(126), _b(127),
_b(128), _b(129), _b(130), _b(131), _b(132), _b(133), _b(134), _b(135),
_b(136), _b(137), _b(138), _b(139), _b(140), _b(141), _b(142), _b(143),
_b(144), _b(145), _b(146), _b(147), _b(148), _b(149), _b(150), _b(151),
_b(152), _b(153), _b(154), _b(155), _b(156), _b(157), _b(158), _b(159),
_b(160), _b(161), _b(162), _b(163), _b(164), _b(165), _b(166), _b(167),
_b(168), _b(169), _b(170), _b(171), _b(172), _b(173), _b(174), _b(175),
_b(176), _b(177), _b(178), _b(179), _b(180), _b(181), _b(182), _b(183),
_b(184), _b(185), _b(186), _b(187), _b(188), _b(189), _b(190), _b(191),
_b(192), _b(193), _b(194), _b(195), _b(196), _b(197), _b(198), _b(199),
_b(200), _b(201), _b(202), _b(203), _b(204), _b(205), _b(206), _b(207),
_b(208), _b(209), _b(210), _b(211), _b(212), _b(213), _b(214), _b(215),
_b(216), _b(217), _b(218), _b(219), _b(220), _b(221), _b(222), _b(223),
_b(224), _b(225), _b(226), _b(227), _b(228), _b(229), _b(230), _b(231),
_b(232), _b(233), _b(234), _b(235), _b(236), _b(237), _b(238), _b(239),
_b(240), _b(241), _b(242), _b(243), _b(244), _b(245), _b(246), _b(247),
_b(248), _b(249), _b(250), _b(251), _b(252), _b(253), _b(254), _b(255)
};
#define UNSCRAMBLE_TABLE_SIZE (sizeof(unscramble_table) / sizeof(unscramble_table[0]))
static const unsigned char xor_table[] = {
/* Set of bytes 0x00 to 0xFF in random order; each byte in an input
string is XORed with successive bytes in this table. */
0x00, 0xCE, 0xB1, 0x9F, 0xE4, 0xE0, 0xE3, 0x79,
0xA1, 0x3B, 0x4E, 0x89, 0x81, 0x84, 0x43, 0xC8,
0xBE, 0x0F, 0x67, 0x2A, 0xB4, 0xD8, 0xBA, 0x5D,
0x94, 0x06, 0x69, 0x0E, 0x1C, 0x48, 0x9E, 0x0A,
0x1D, 0x09, 0x02, 0xCD, 0xD4, 0xF6, 0x5B, 0x8A,
0xAE, 0x65, 0xB3, 0xB5, 0xA7, 0x13, 0x03, 0xF2,
0x42, 0xF0, 0xA6, 0xAA, 0x35, 0xCB, 0x2C, 0x55,
0xF5, 0xC7, 0x32, 0xB7, 0x6B, 0xEA, 0xC3, 0x6F,
0x41, 0xFF, 0xD1, 0x24, 0x54, 0xA9, 0xC6, 0xC2,
0x74, 0xEE, 0xBC, 0x99, 0x59, 0x71, 0x3D, 0x85,
0x0B, 0xF7, 0x3A, 0x7E, 0xDB, 0x45, 0xE8, 0x96,
0xD0, 0xC1, 0xE6, 0xFD, 0x86, 0x8C, 0x9B, 0x0C,
0x66, 0x5F, 0xE5, 0x14, 0x98, 0x3C, 0xBD, 0xE2,
0x88, 0xA3, 0x30, 0x38, 0x2F, 0xA2, 0x37, 0x70,
0xB8, 0x11, 0x61, 0x93, 0x52, 0x1B, 0xDD, 0x20,
0x60, 0x19, 0xEF, 0xD2, 0xEC, 0x73, 0x07, 0x92,
0x4C, 0x6A, 0xA8, 0x9D, 0x34, 0x04, 0x87, 0x2E,
0x1E, 0xA4, 0xCA, 0x72, 0x63, 0xD7, 0x7F, 0xFB,
0x68, 0xE1, 0xBF, 0x10, 0x8E, 0xAF, 0x9A, 0xFA,
0xA0, 0xDE, 0x1F, 0x31, 0x15, 0x97, 0xED, 0x2B,
0x36, 0x8D, 0x12, 0xC5, 0x23, 0x95, 0x33, 0x56,
0x4F, 0xE7, 0xAD, 0x5C, 0x4B, 0x83, 0xDC, 0x29,
0xE9, 0xCF, 0x8F, 0x58, 0x4D, 0x5A, 0x08, 0x49,
0xFC, 0x6D, 0x7C, 0xB6, 0xD3, 0x7B, 0xD6, 0x53,
0x57, 0x82, 0x0D, 0xD9, 0x7D, 0xDA, 0x4A, 0xDF,
0x27, 0x40, 0x1A, 0x22, 0xC9, 0x51, 0x3E, 0x6C,
0xC4, 0x18, 0xCC, 0xAC, 0xEB, 0xA5, 0xF4, 0x44,
0xFE, 0x76, 0xF8, 0x75, 0xF3, 0x2D, 0xB0, 0xB9,
0x9C, 0x47, 0x7A, 0x28, 0xBB, 0xF1, 0x16, 0x64,
0x46, 0x21, 0x78, 0x90, 0xD5, 0x80, 0x3F, 0x39,
0x25, 0xB2, 0x6E, 0x8B, 0x77, 0xC0, 0x05, 0x50,
0x17, 0xF9, 0x01, 0x26, 0x91, 0x5E, 0x62, 0xAB
};
#define XOR_TABLE_SIZE (sizeof(xor_table) / sizeof(xor_table[0]))
/************************************************************************
* Module-specific variables *
************************************************************************/
static char *home_directory_str = NULL; // Full pathname to home
static char *data_directory_str = NULL; // Writable data dir pathname
static int data_directory_fd = -1; // Data dir file descriptor
static bool is_posix_locale = false; // Override strfmon()?
/************************************************************************
* Module-specific function prototypes *
************************************************************************/
/*
Function: home_directory - Return home directory pathname
Parameters: (none)
Returns: const char * - Pointer to home directory
This function returns the full pathname to the user's home directory,
using the HOME environment variable. Note that the existence of or
ability to write to this pathname is NOT checked by this function.
NULL is returned if the home directory cannot be determined.
*/
static const char *home_directory (void);
/*
Function: game_rel_filename - Generate a relative game filename
Parameters: gamenum - Game number (1-9) as an integer
Returns: char * - Pointer to game filename string
This function returns a game filename, as a malloc()ed string, relative
to some current directory. If gamenum is outside the range 1 to 9
(inclusive), NULL is returned.
*/
extern char *game_rel_filename (int gamenum);
/*
Function: apply_xor - Scramble a buffer using xor_table
Parameters: dest - Location of destination buffer
src - Location of source buffer
n - Number of bytes to scramble
key - Pointer to xor_table index
Returns: (nothing)
This function copies n bytes from *src into *dest, applying a XOR with
the contents of xor_table in the process. It is a reversible function:
apply_xor(apply_xor(buffer)) == buffer. It is used by both scramble()
and unscramble().
*/
static void apply_xor (char *restrict dest, const char *restrict src,
size_t n, unsigned int *restrict key);
/*
Function: b64encode - Convert a block to non-standard Base64 encoding
Parameters: in - Location of input buffer
inlen - Size of input buffer
out - Location of output buffer
outlen - Size of output buffer
Returns: size_t - Number of bytes placed in output buffer
This function encodes inlen bytes in the input buffer into the output
buffer using a non-standard Base64 encoding (as contained above in
scramble_table[]). The resulting encoded string length is returned
(including trailing '\n' but NOT including trailing NUL).
Note that the output buffer must be at least 4/3 the size of the input
buffer; if not, an assert is generated.
This function is used by scramble().
*/
static size_t b64encode (const void *restrict in, size_t inlen,
void *restrict out, size_t outlen);
/*
Function: b64decode - Convert a block from non-standard Base64 encoding
Parameters: in - Location of input buffer
inlen - Size of input buffer
out - Location of output buffer
outlen - Size of output buffer
Returns: ssize_t - Number of bytes placed in output buffer, or -1
This function decodes up to inlen bytes in the input buffer into the
output buffer using a non-standard Base64 encoding (as contained above
in unscramble_table[]). The resulting decoded buffer length is
returned; that buffer may contain NUL bytes. If an error occurs during
decoding, -1 is returned instead.
This function is used by unscramble().
*/
static ssize_t b64decode (const void *restrict in, size_t inlen,
void *restrict out, size_t outlen);
/************************************************************************
* Initialisation and environment function definitions *
************************************************************************/
/* These functions are documented in the file "utils.h" or in the
comments above. */
/***********************************************************************/
// init_program_name: Make the program name canonical
void init_program_name (const char *argv0)
{
/* This implementation assumes a POSIX environment with an ASCII-safe
character encoding (such as ASCII or UTF-8). */
const char *dirsep = DIRSEP;
assert(strlen(dirsep) == 1);
if (argv0 == NULL || *argv0 == '\0') {
program_name = PACKAGE;
} else {
char *p = strrchr(argv0, dirsep[0]);
if (p != NULL && *++p != '\0') {
program_name = xstrdup(p);
} else {
program_name = xstrdup(argv0);
}
// Prevent the AppImage internal name from leaking out
if (strcmp(program_name, APPIMAGE_NAME) == 0) {
free((void *) program_name);
program_name = PACKAGE;
}
}
}
/***********************************************************************/
// home_directory: Return home directory pathname
const char *home_directory (void)
{
if (home_directory_str == NULL) {
// Use the HOME environment variable where possible
const char *home = getenv("HOME");
if (home != NULL && *home != '\0') {
home_directory_str = xstrdup(home);
} else {
home_directory_str = xstrdup(CURDIR);
}
}
return home_directory_str;
}
/***********************************************************************/
// data_directory: Return writable data directory pathname
const char *data_directory (void)
{
/* This implementation assumes a POSIX environment by using DIRSEP as
the directory separator. It also assumes a dot-starting directory
name is permissible (again, true on POSIX systems) and that the
character encoding is ASCII-safe. */
const char *dirsep = DIRSEP;
const char *dirsep_hiddenpath = DIRSEP HIDDEN_PATH;
const char *datahome_part = DIRSEP XDG_DATA_DEFAULT DIRSEP;
assert(strlen(dirsep) == 1);
if (data_directory_str == NULL) {
const char *home = home_directory();
const char *xdg_data_home = getenv("XDG_DATA_HOME");
if (program_name != NULL) {
int len_program_name = strlen(program_name);
if (home != NULL) {
char *p = xmalloc(strlen(home) + strlen(dirsep_hiddenpath)
+ len_program_name + 1);
strcpy(p, home);
strcat(p, dirsep_hiddenpath);
strcat(p, program_name);
data_directory_fd = open(p, O_DIRECTORY | O_SEARCH);
if (data_directory_fd != -1) {
// Directory "$HOME/.trader" exists: use that
data_directory_str = p;
} else {
free(p);
}
}
if (data_directory_str == NULL && xdg_data_home != NULL
&& *xdg_data_home != '\0' && *xdg_data_home == dirsep[0]) {
// Use directory "$XDG_DATA_HOME/trader"
/* Note that the XDG Base Directory Specification v0.7
requires XDG_DATA_HOME to contain an absolute path:
the variable must be ignored if it does not start with
the directory separator DIRSEP ("/") */
char *p = xmalloc(strlen(xdg_data_home) + strlen(DIRSEP)
+ len_program_name + 1);
strcpy(p, xdg_data_home);
strcat(p, dirsep);
strcat(p, program_name);
data_directory_str = p;
}
if (data_directory_str == NULL && home != NULL) {
// Use directory "$HOME/.local/share/trader"
char *p = xmalloc(strlen(home) + strlen(datahome_part)
+ len_program_name + 1);
strcpy(p, home);
strcat(p, datahome_part);
strcat(p, program_name);
data_directory_str = p;
}
}
}
if (data_directory_str != NULL && data_directory_fd == -1) {
data_directory_fd = open(data_directory_str, O_DIRECTORY | O_SEARCH);
}
return data_directory_str;
}
/***********************************************************************/
// data_directory_fileno: Return file descriptor of data directory
int data_directory_fileno (void)
{
if (data_directory_fd == -1) {
(void) data_directory();
}
return data_directory_fd;
}
/***********************************************************************/
// game_rel_filename: Generate a relative game filename
char *game_rel_filename (int gamenum)
{
/* This implementation assumes a POSIX environment and an ASCII-safe
character encoding. */
char *p;
if (gamenum < 1 || gamenum > 9) {
return NULL;
}
p = xmalloc(GAME_FILENAME_BUFSIZE);
snprintf(p, GAME_FILENAME_BUFSIZE, GAME_FILENAME_PROTO, gamenum);
return p;
}
/***********************************************************************/
// game_filename: Convert an integer to a game filename
char *game_filename (int gamenum)
{
/* This implementation assumes a POSIX environment and an ASCII-safe
character encoding. */
const char *dirsep = DIRSEP;
char *gfn; // Relative game filename
const char *dd; // Data directory
gfn = game_rel_filename(gamenum);
if (gfn == NULL) {
return NULL;
}
dd = data_directory();
if (dd == NULL) {
return gfn;
} else {
char *p = xmalloc(strlen(dd) + strlen(dirsep) + strlen(gfn) + 1);
strcpy(p, dd);
strcat(p, dirsep);
strcat(p, gfn);
free(gfn);
return p;
}
}
/***********************************************************************/
// game_fopen: Open a game file for reading or writing
FILE *game_fopen (int gamenum, const char *mode)
{
char *gfn;
int ddfd;
int fd;
int oflag;
mode_t omode;
gfn = game_rel_filename(gamenum);
if (gfn == NULL) {
return NULL;
}
ddfd = data_directory_fileno();
if (ddfd == -1) {
ddfd = AT_FDCWD;
}
if (strcmp(mode, "r") == 0) {
oflag = O_RDONLY;
omode = 0;
} else if (strcmp(mode, "w") == 0) {
oflag = O_WRONLY | O_CREAT | O_TRUNC;
omode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
} else {
free(gfn);
errno = EINVAL;
return NULL;
}
fd = openat(ddfd, gfn, oflag, omode);
free(gfn);
return (fd == -1) ? NULL : fdopen(fd, mode);
}
/************************************************************************
* Error-reporting function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// err_exit: Print an error and exit
void err_exit (const char *restrict format, ...)
{
va_list args;
end_screen();
fprintf(stderr, _("%s: "), program_name);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputs("\n", stderr);
exit(EXIT_FAILURE);
}
/***********************************************************************/
// errno_exit: Print an error message (using errno) and exit
void errno_exit (const char *restrict format, ...)
{
va_list args;
int saved_errno = errno;
end_screen();
fprintf(stderr, _("%s: "), program_name);
if (format != NULL) {
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputs(_(": "), stderr);
}
fprintf(stderr, "%s\n", strerror(saved_errno));
exit(EXIT_FAILURE);
}
/***********************************************************************/
// err_exit_nomem: Print an "out of memory" error and exit
void err_exit_nomem (void)
{
err_exit(_("out of memory"));
}
/************************************************************************
* Random-number function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// init_rand: Initialise the random number generator
void init_rand (void)
{
/* Ideally, initialisation of the random number generator should be
made using seed48() and lcong48(). However, since this is "only a
game", 32 bits of "randomness" as returned by gettimeofday() is
probably more than enough... */
struct timeval tv;
unsigned long int seed;
gettimeofday(&tv, NULL); // If this fails, tv is random enough!
seed = tv.tv_sec + tv.tv_usec;
srand48(seed);
}
/***********************************************************************/
// randf: Return a random number between 0.0 and 1.0
extern double randf (void)
{
return drand48();
}
/***********************************************************************/
// randi: Return a random number between 0 and limit
extern int randi (int limit)
{
return drand48() * (double) limit;
}
/************************************************************************
* Locale-aware function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// init_locale: Initialise locale and message catalogs
extern void init_locale (void)
{
const char *podir = getenv("TEXTDOMAINDIR");
// Initialise the current locale
if (setlocale(LC_ALL, "") == NULL) {
err_exit("could not set locale "
"(check LANG, LC_ALL and LANGUAGE in environment)");
}
// Use correct message catalogs for the locale
bindtextdomain(
PACKAGE,
(podir != NULL && *podir != '\0') ? podir : LOCALEDIR);
textdomain(PACKAGE);
}
/***********************************************************************/
// init_locale_vars: Initialise locale-specific variables
void init_locale_vars (void)
{
char *cur, *cloc;
struct lconv *lc;
wchar_t *buf;
cur = xstrdup(setlocale(LC_MONETARY, NULL));
lc = localeconv();
assert(lc != NULL);
is_posix_locale = false;
lconvinfo = *lc;
/* Are we in the POSIX locale? The string returned by setlocale() is
supposed to be opaque, but in practise is not. To be on the safe
side, we explicitly set the locale to "C", then test the returned
value of that, too. */
cloc = setlocale(LC_MONETARY, "C");
if ( strcmp(cur, cloc) == 0
|| strcmp(cur, "POSIX") == 0
|| strcmp(cur, "POSIX.UTF-8") == 0
|| strcmp(cur, "POSIX.utf8") == 0
|| strcmp(cur, "C") == 0
|| strcmp(cur, "C.UTF-8") == 0
|| strcmp(cur, "C.utf8") == 0) {
is_posix_locale = true;
lconvinfo = mod_posix_lconv;
}
// Convert localeconv() information to wide strings
buf = xmalloc(BUFSIZE * sizeof(wchar_t));
xmbstowcs(buf, lconvinfo.decimal_point, BUFSIZE);
decimal_point = xwcsdup(buf);
xmbstowcs(buf, lconvinfo.thousands_sep, BUFSIZE);
thousands_sep = xwcsdup(buf);
xmbstowcs(buf, lconvinfo.currency_symbol, BUFSIZE);
currency_symbol = xwcsdup(buf);
xmbstowcs(buf, lconvinfo.mon_decimal_point, BUFSIZE);
mon_decimal_point = xwcsdup(buf);
xmbstowcs(buf, lconvinfo.mon_thousands_sep, BUFSIZE);
mon_thousands_sep = xwcsdup(buf);
free(buf);
setlocale(LC_MONETARY, cur);
free(cur);
}
/***********************************************************************/
// xwcsfmon: Convert monetary value to a wide-character string
ssize_t xwcsfmon (wchar_t *restrict buf, size_t maxsize,
const char *restrict format, double val)
{
ssize_t n;
char *s = xmalloc(BUFSIZE);
/* Current and previous versions of ISO/IEC 9945-1 (POSIX), namely
SUSv3 (2001) and SUSv4 (2008), require strfmon() to return rather
meaningless strings when used with the POSIX "C" locale. In
particular, the standard POSIX locale does not define a currency
symbol, a monetary radix symbol (decimal point) or a negative
sign. This means strfmon(..., "%n", -123.45) is supposed to
produce "12345" instead of something like "-$123.45"! The
following code overcomes these limitations by using snprintf(). */
if (! is_posix_locale) {
n = strfmon(s, BUFSIZE, format, val);
} else {
/* The current implementation assumes the monetary decimal point
is overridden to "." (ie, MOD_POSIX_MON_DECIMAL_POINT == "."),
the currency symbol is to precede the value, no spaces are to
separate currency symbol and value, and sign is to precede
both currency symbol and value. */
assert(MOD_POSIX_P_CS_PRECEDES == 1);
assert(MOD_POSIX_P_SEP_BY_SPACE == 0);
assert(MOD_POSIX_N_CS_PRECEDES == 1);
assert(MOD_POSIX_N_SEP_BY_SPACE == 0);
assert(MOD_POSIX_P_SIGN_POSN == 1);
assert(MOD_POSIX_N_SIGN_POSN == 1);
# define MOD_POSIX_q(s) MOD_POSIX_qq(s)
# define MOD_POSIX_qq(s) #s
# define MOD_POSIX_FMT_POS MOD_POSIX_POSITIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
# define MOD_POSIX_FMT_NEG MOD_POSIX_NEGATIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
# define MOD_POSIX_FMT_POS_NOSYM MOD_POSIX_POSITIVE_SIGN "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
# define MOD_POSIX_FMT_NEG_NOSYM MOD_POSIX_NEGATIVE_SIGN "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
if (strcmp(format, "%n") == 0) {
if (val >= 0.0) {
n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_POS, val);
} else {
n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_NEG, -val);
}
} else if (strcmp(format, "%!n") == 0) {
if (val >= 0.0) {
n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_POS_NOSYM, val);
} else {
n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_NEG_NOSYM, -val);
}
} else {
// Other strfmon() formats are not supported
errno = EINVAL;
n = -1;
}
}
if (n >= BUFSIZE) {
// Truncate the too-long output with a terminating NUL
s[BUFSIZE - 1] = '\0';
}
if (n >= 0) {
xmbstowcs(buf, s, maxsize);
/* Some buggy implementations of strfmon(), such as that on
FreeBSD and Cygwin, assume localeconv.mon_thousands_sep and
similar strings contain either a single char or NUL instead of
a multibyte character string. However, this assumption fails
on locales such as ru_RU.UTF-8 which use U+00A0 NO-BREAK SPACE
for mon_thousands_sep (stored in UTF-8 as 0xC2 0xA0. As a
result, incomplete character sequences are copied, which are
translated to EILSEQ_REPL_WC characters by xmbstowcs() above.
Fix such characters by replacing them with a space. */
for (wchar_t *p = buf; *p != L'\0'; p++) {
if (*p == EILSEQ_REPL_WC) {
*p = L' ';
}
}
}
free(s);
return n;
}
/************************************************************************
* Encryption function definitions *
************************************************************************/
/* These functions are documented in the file "utils.h" or in the
comments above. */
/***********************************************************************/
// scramble: Scramble (encrypt) the buffer
char *scramble (char *restrict dest, const char *restrict src,
size_t size, unsigned int *restrict key)
{
unsigned long int crc;
unsigned int chksum;
size_t srclen;
char *xorbuf, *midxor;
char *middest;
char crcbuf[SCRAMBLE_CRC_LEN + 1];
char chksumbuf[SCRAMBLE_CHKSUM_LEN + 1];
assert(dest != NULL);
assert(src != NULL);
assert(size > 0);
srclen = strlen(src);
if (key == NULL) {
// No encryption required
assert(size >= srclen + 2); // Enough room to add "\n"?
strcpy(dest, src);
// Add "\n" if needed
if (dest[srclen - 1] != '\n') {
dest[srclen] = '\n';
dest[srclen + 1] = '\0';
}
} else {
// Scramble the input
xorbuf = xmalloc(srclen + SCRAMBLE_CRC_LEN + 1);
// Scramble src using *key, leaving room for CRC32 in front
midxor = xorbuf + SCRAMBLE_CRC_LEN;
apply_xor(midxor, src, srclen, key);
// Calculate CRC32 checksum of XORed buffer
crc = crc32(midxor, srclen) & SCRAMBLE_CRC_MASK;
snprintf(crcbuf, SCRAMBLE_CRC_LEN + 1, "%08lx", crc);
memcpy(xorbuf, crcbuf, SCRAMBLE_CRC_LEN);
// Encode whole buffer (including CRC32) using Base64
middest = dest + SCRAMBLE_CHKSUM_LEN;
b64encode(xorbuf, srclen + SCRAMBLE_CRC_LEN,
middest, size - SCRAMBLE_CHKSUM_LEN);
// Calculate simple checksum
chksum = 0;
for (char *p = middest; *p != '\0' && *p != '\n'; p++) {
chksum += *p;
}
chksum &= SCRAMBLE_CHKSUM_MASK;
// Place checksum in front of Base64 string
snprintf(chksumbuf, SCRAMBLE_CHKSUM_LEN + 1, "%03x", chksum);
memcpy(dest, chksumbuf, SCRAMBLE_CHKSUM_LEN);
dest[size - 1] = '\0'; // Keep Coverity Scan happy...
free(xorbuf);
}
return dest;
}
/***********************************************************************/
// unscramble: Unscramble (decrypt) the buffer
char *unscramble (char *restrict dest, const char *restrict src,
size_t size, unsigned int *restrict key)
{
unsigned long int crc, crc_input;
unsigned int chksum, chksum_input;
size_t srclen;
char *xorbuf, *midxor;
ssize_t xorlen;
const char *midsrc;
char crcbuf[SCRAMBLE_CRC_LEN + 2]; // Leave room for '\n\0'
char chksumbuf[SCRAMBLE_CHKSUM_LEN + 2];
assert(dest != NULL);
assert(src != NULL);
assert(size > 0);
srclen = strlen(src);
if (key == NULL) {
// No decryption required
assert(size >= srclen + 1);
strcpy(dest, src);
} else {
// Unscramble the input
// Copy out simple checksum from input
memcpy(chksumbuf, src, SCRAMBLE_CHKSUM_LEN);
chksumbuf[SCRAMBLE_CHKSUM_LEN] = '\n';
chksumbuf[SCRAMBLE_CHKSUM_LEN + 1] = '\0';
if (sscanf(chksumbuf, "%x\n", &chksum_input) != 1) {
return NULL;
}
// Calculate and compare checksums
midsrc = src + SCRAMBLE_CHKSUM_LEN;
chksum = 0;
for (const char *p = midsrc; *p != '\0' && *p != '\n'; p++) {
chksum += *p;
}
chksum &= SCRAMBLE_CHKSUM_MASK;
if (chksum != chksum_input) {
return NULL;
}
xorbuf = xmalloc(size + SCRAMBLE_CRC_LEN);
// Decode buffer sans checksum using Base64
xorlen = b64decode(midsrc, srclen - SCRAMBLE_CHKSUM_LEN,
xorbuf, size + SCRAMBLE_CRC_LEN);
if (xorlen < SCRAMBLE_CRC_LEN) {
free(xorbuf);
return NULL;
}
// Copy out CRC32 checksum
memcpy(crcbuf, xorbuf, SCRAMBLE_CRC_LEN);
crcbuf[SCRAMBLE_CRC_LEN] = '\n';
crcbuf[SCRAMBLE_CRC_LEN + 1] = '\0';
if (sscanf(crcbuf, "%lx\n", &crc_input) != 1) {
free(xorbuf);
return NULL;
}
// Calculate and compare CRC32 checksums
midxor = xorbuf + SCRAMBLE_CRC_LEN;
crc = crc32(midxor, xorlen - SCRAMBLE_CRC_LEN) & SCRAMBLE_CRC_MASK;
if (crc != crc_input) {
free(xorbuf);
return NULL;
}
// Unscramble xorbuf using *key, ignoring CRC32 in front
apply_xor(dest, midxor, xorlen - SCRAMBLE_CRC_LEN, key);
// Convert the output to a C string
assert(size >= (size_t) xorlen - SCRAMBLE_CRC_LEN + 1);
dest[xorlen - SCRAMBLE_CRC_LEN] = '\0';
free(xorbuf);
}
return dest;
}
/***********************************************************************/
// apply_xor: Scramble a buffer using xor_table
void apply_xor (char *restrict dest, const char *restrict src,
size_t n, unsigned int *restrict key)
{
assert(dest != NULL);
assert(src != NULL);
assert(key != NULL);
assert(*key < XOR_TABLE_SIZE);
for (size_t i = 0; i < n; i++, dest++, src++) {
*(unsigned char *) dest = *(unsigned char *) src ^ xor_table[*key];
*key = (*key + 1) % XOR_TABLE_SIZE;
}
}
/***********************************************************************/
// b64encode: Convert a block to non-standard Base64 encoding
size_t b64encode (const void *restrict in, size_t inlen,
void *restrict out, size_t outlen)
{
size_t count;
size_t padding;
// Note that bit manipulations on strings require unsigned char!
const unsigned char *u_in = in;
unsigned char *u_out = out;
assert(u_in != NULL);
assert(u_out != NULL);
assert(outlen > 0);
assert(outlen > inlen);
count = 0;
padding = inlen % 3;
for (size_t i = 0; i < inlen; i += 3, u_in += 3) {
unsigned long int n;
unsigned char n0, n1, n2, n3;
// Convert three input bytes into a 24-bit number
n = u_in[0] << 16;
if (i + 1 < inlen) {
n += u_in[1] << 8;
}
if (i + 2 < inlen) {
n += u_in[2];
}
// Convert the 24-bit number into four Base64 bytes
n0 = (unsigned char) (n >> 18) & 0x3F;
n1 = (unsigned char) (n >> 12) & 0x3F;
n2 = (unsigned char) (n >> 6) & 0x3F;
n3 = (unsigned char) n & 0x3F;
assert(count + 3 < outlen);
*u_out++ = scramble_table[n0];
*u_out++ = scramble_table[n1];
count += 2;
if (i + 1 < inlen) {
*u_out++ = scramble_table[n2];
count++;
}
if (i + 2 < inlen) {
*u_out++ = scramble_table[n3];
count++;
}
}
if (padding > 0) {
assert(count + 2 < outlen);
for ( ; padding < 3; padding++) {
*u_out++ = SCRAMBLE_PAD_CHAR;
count++;
}
}
assert(count + 2 <= outlen);
*u_out++ = '\n';
*u_out = '\0';
count++;
return count;
}
/***********************************************************************/
// b64decode: Convert a block from non-standard Base64 encoding
ssize_t b64decode (const void *restrict in, size_t inlen,
void *restrict out, size_t outlen)
{
size_t count;
unsigned long int n;
// Note that bit manipulations on strings require unsigned char!
// Using char * results in very subtle bugs indeed...
const unsigned char *u_in = in;
unsigned char *u_out = out;
assert(u_in != NULL);
assert(u_out != NULL);
assert(outlen > 0);
assert(UNSCRAMBLE_TABLE_SIZE == UCHAR_MAX + 1);
count = 0;
n = 1;
for (size_t i = 0; i < inlen && *u_in != '\0'; i++, u_in++) {
int v = unscramble_table[*u_in];
switch (v) {
case UNSCRAMBLE_INVALID:
return -1;
case UNSCRAMBLE_IGNORE:
continue;
case UNSCRAMBLE_PAD_CHAR:
// Assume end of data
i = inlen;
continue;
default:
n = n << 6 | v; // v is 0 .. 63
if (n & 0x1000000) {
// Convert 24-bit number into three output bytes
count += 3;
if (count > outlen) {
return -1;
}
*u_out++ = n >> 16;
*u_out++ = n >> 8;
*u_out++ = n;
n = 1;
}
}
}
if (n & 0x40000) {
count += 2;
if (count > outlen) {
return -1;
}
*u_out++ = n >> 10;
*u_out++ = n >> 2;
} else if (n & 0x1000) {
count += 1;
if (count > outlen) {
return -1;
}
*u_out++ = n >> 4;
}
return count;
}
/************************************************************************
* Miscellaneous function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// xmkdir: Check and create directory with its parents
int xmkdir (const char *pathname, mode_t mode)
{
const char *dirsep = DIRSEP;
struct stat statbuf;
char *pathcopy;
char *pcomp, *pend;
int ret;
assert(strlen(dirsep) == 1);
if (pathname == NULL || *pathname == '\0') {
errno = ENOENT; // As documented by POSIX
return -1;
}
// Try creating the directory
ret = mkdir(pathname, mode);
if (ret != 0 && errno == EEXIST) {
// Pathname exists: is it a directory?
if (stat(pathname, &statbuf) == 0) {
if (S_ISDIR(statbuf.st_mode)) {
return 0;
} else {
errno = ENOTDIR;
return -1;
}
} else {
return -1;
}
} else if (ret == 0 || (errno != ENOENT && errno != ENOTDIR)) {
return ret;
}
// Try creating directory components, except the last, one by one
pathcopy = xstrdup(pathname);
pcomp = pend = pathcopy;
for ( ; *pend != '\0'; pend++) {
if (*pend == dirsep[0] && pcomp != pend) {
*pend = '\0';
ret = mkdir(pathcopy, mode);
if (ret != 0 && errno != EEXIST) {
free(pathcopy);
return ret;
}
*pend = dirsep[0];
pcomp = pend + 1;
}
}
// Try creating the last directory component
ret = mkdir(pathcopy, mode);
free(pathcopy);
return ret;
}
/***********************************************************************/
// xmalloc: Allocate a new block of memory, with checking
void *xmalloc (size_t size)
{
void *p;
if (size < 1)
size = 1;
p = malloc(size);
if (p == NULL) {
err_exit_nomem();
}
return p;
}
/***********************************************************************/
// xstrdup: Duplicate a string, with checking
char *xstrdup (const char *str)
{
char *s;
if (str == NULL)
str = "";
s = strdup(str);
if (s == NULL) {
err_exit_nomem();
}
return s;
}
/***********************************************************************/
// chstrdup: Duplicate a chtype buffer
chtype *xchstrdup (const chtype *restrict chstr)
{
const chtype *p;
int len;
chtype *ret;
// Determine chstr length, including ending NUL
for (len = 1, p = chstr; *p != '\0'; p++, len++)
;
ret = xmalloc(len * sizeof(chtype));
memcpy(ret, chstr, len * sizeof(chtype));
ret[len - 1] = '\0'; // Terminating NUL, just in case not present
return ret;
}
/***********************************************************************/
// xwcsdup: Duplicate a wide-character string, with checking
wchar_t *xwcsdup (const wchar_t *str)
{
wchar_t *s;
if (str == NULL)
str = L"";
s = wcsdup(str);
if (s == NULL) {
err_exit_nomem();
}
return s;
}
/***********************************************************************/
// xmbstowcs: Convert a multibyte string to a wide-character string
size_t xmbstowcs (wchar_t *restrict dest, const char *restrict src, size_t len)
{
assert(dest != NULL);
assert(len > 0);
char *s = xstrdup(src);
size_t n;
while (true) {
mbstate_t mbstate;
char *p = s;
memset(&mbstate, 0, sizeof(mbstate));
if ((n = mbsrtowcs(dest, (const char **) &p, len, &mbstate))
== (size_t) -1) {
if (errno == EILSEQ) {
// Illegal sequence detected: replace it and try again
*p = EILSEQ_REPL;
} else {
errno_exit(_("xmbstowcs: '%s'"), src);
}
} else if (p != NULL) {
// Multibyte string was too long: truncate dest
dest[len - 1] = L'\0';
n--;
break;
} else {
break;
}
}
free(s);
return n;
}
/***********************************************************************/
// xwcrtomb: Convert a wide character to a multibyte sequence
size_t xwcrtomb (char *restrict dest, wchar_t wc, mbstate_t *restrict mbstate)
{
mbstate_t mbcopy;
size_t n;
assert(dest != NULL);
assert(mbstate != NULL);
memcpy(&mbcopy, mbstate, sizeof(mbcopy));
if ((n = wcrtomb(dest, wc, &mbcopy)) == (size_t) -1) {
if (errno == EILSEQ) {
/* wc cannot be represented in current locale.
Note that the shift state in mbcopy is now undefined.
Hence, restore the original, try to store an ending shift
sequence, then EILSEQ_REPL. */
memcpy(&mbcopy, mbstate, sizeof(mbcopy));
if ((n = wcrtomb(dest, L'\0', &mbcopy)) == (size_t) -1) {
errno_exit(_("xwcrtomb: NUL"));
}
dest[n++] = EILSEQ_REPL;
dest[n] = '\0';
} else {
errno_exit(_("xwcrtomb: '%lc'"), (wint_t) wc);
}
}
memcpy(mbstate, &mbcopy, sizeof(mbcopy));
return n;
}
/***********************************************************************/
// End of file