mirror of
https://git.zap.org.au/git/trader.git
synced 2024-11-03 17:27:29 -05:00
1431 lines
42 KiB
C
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
|