Path: blob/master/thirdparty/sdl/stdlib/SDL_string.c
10279 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122// This file contains portable string manipulation functions for SDL2324#include "SDL_vacopy.h"2526#ifdef SDL_PLATFORM_VITA27#include <psp2/kernel/clib.h>28#endif2930#include "SDL_sysstdlib.h"3132#include "SDL_casefolding.h"3334#if defined(__SIZEOF_WCHAR_T__)35#define SDL_SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__36#elif defined(SDL_PLATFORM_WINDOWS)37#define SDL_SIZEOF_WCHAR_T 238#else // assume everything else is UTF-32 (add more tests if compiler-assert fails below!)39#define SDL_SIZEOF_WCHAR_T 440#endif41SDL_COMPILE_TIME_ASSERT(sizeof_wchar_t, sizeof(wchar_t) == SDL_SIZEOF_WCHAR_T);424344char *SDL_UCS4ToUTF8(Uint32 codepoint, char *dst)45{46if (!dst) {47return NULL; // I guess...?48} else if (codepoint > 0x10FFFF) { // Outside the range of Unicode codepoints (also, larger than can be encoded in 4 bytes of UTF-8!).49codepoint = SDL_INVALID_UNICODE_CODEPOINT;50} else if ((codepoint >= 0xD800) && (codepoint <= 0xDFFF)) { // UTF-16 surrogate values are illegal in UTF-8.51codepoint = SDL_INVALID_UNICODE_CODEPOINT;52}5354Uint8 *p = (Uint8 *)dst;55if (codepoint <= 0x7F) {56*p = (Uint8)codepoint;57++dst;58} else if (codepoint <= 0x7FF) {59p[0] = 0xC0 | (Uint8)((codepoint >> 6) & 0x1F);60p[1] = 0x80 | (Uint8)(codepoint & 0x3F);61dst += 2;62} else if (codepoint <= 0xFFFF) {63p[0] = 0xE0 | (Uint8)((codepoint >> 12) & 0x0F);64p[1] = 0x80 | (Uint8)((codepoint >> 6) & 0x3F);65p[2] = 0x80 | (Uint8)(codepoint & 0x3F);66dst += 3;67} else {68SDL_assert(codepoint <= 0x10FFFF);69p[0] = 0xF0 | (Uint8)((codepoint >> 18) & 0x07);70p[1] = 0x80 | (Uint8)((codepoint >> 12) & 0x3F);71p[2] = 0x80 | (Uint8)((codepoint >> 6) & 0x3F);72p[3] = 0x80 | (Uint8)(codepoint & 0x3F);73dst += 4;74}7576return dst;77}787980// this expects `from` and `to` to be UTF-32 encoding!81int SDL_CaseFoldUnicode(Uint32 from, Uint32 *to)82{83// !!! FIXME: since the hashtable is static, maybe we should binary84// !!! FIXME: search it instead of walking the whole bucket.8586if (from < 128) { // low-ASCII, easy!87if ((from >= 'A') && (from <= 'Z')) {88*to = 'a' + (from - 'A');89return 1;90}91} else if (from <= 0xFFFF) { // the Basic Multilingual Plane.92const Uint8 hash = ((from ^ (from >> 8)) & 0xFF);93const Uint16 from16 = (Uint16) from;9495// see if it maps to a single char (most common)...96{97const CaseFoldHashBucket1_16 *bucket = &case_fold_hash1_16[hash];98const int count = (int) bucket->count;99for (int i = 0; i < count; i++) {100const CaseFoldMapping1_16 *mapping = &bucket->list[i];101if (mapping->from == from16) {102*to = mapping->to0;103return 1;104}105}106}107108// see if it folds down to two chars...109{110const CaseFoldHashBucket2_16 *bucket = &case_fold_hash2_16[hash & 15];111const int count = (int) bucket->count;112for (int i = 0; i < count; i++) {113const CaseFoldMapping2_16 *mapping = &bucket->list[i];114if (mapping->from == from16) {115to[0] = mapping->to0;116to[1] = mapping->to1;117return 2;118}119}120}121122// okay, maybe it's _three_ characters!123{124const CaseFoldHashBucket3_16 *bucket = &case_fold_hash3_16[hash & 3];125const int count = (int) bucket->count;126for (int i = 0; i < count; i++) {127const CaseFoldMapping3_16 *mapping = &bucket->list[i];128if (mapping->from == from16) {129to[0] = mapping->to0;130to[1] = mapping->to1;131to[2] = mapping->to2;132return 3;133}134}135}136137} else { // codepoint that doesn't fit in 16 bits.138const Uint8 hash = ((from ^ (from >> 8)) & 0xFF);139const CaseFoldHashBucket1_32 *bucket = &case_fold_hash1_32[hash & 15];140const int count = (int) bucket->count;141for (int i = 0; i < count; i++) {142const CaseFoldMapping1_32 *mapping = &bucket->list[i];143if (mapping->from == from) {144*to = mapping->to0;145return 1;146}147}148}149150// Not found...there's no folding needed for this codepoint.151*to = from;152return 1;153}154155#define UNICODE_STRCASECMP(bits, slen1, slen2, update_slen1, update_slen2) \156Uint32 folded1[3], folded2[3]; \157int head1 = 0, tail1 = 0, head2 = 0, tail2 = 0; \158while (true) { \159Uint32 cp1, cp2; \160if (head1 != tail1) { \161cp1 = folded1[tail1++]; \162} else { \163const Uint##bits *str1start = (const Uint##bits *) str1; \164head1 = SDL_CaseFoldUnicode(StepUTF##bits(&str1, slen1), folded1); \165update_slen1; \166cp1 = folded1[0]; \167tail1 = 1; \168} \169if (head2 != tail2) { \170cp2 = folded2[tail2++]; \171} else { \172const Uint##bits *str2start = (const Uint##bits *) str2; \173head2 = SDL_CaseFoldUnicode(StepUTF##bits(&str2, slen2), folded2); \174update_slen2; \175cp2 = folded2[0]; \176tail2 = 1; \177} \178if (cp1 < cp2) { \179return -1; \180} else if (cp1 > cp2) { \181return 1; \182} else if (cp1 == 0) { \183break; /* complete match. */ \184} \185} \186return 0187188189static Uint32 StepUTF8(const char **_str, const size_t slen)190{191/*192* From rfc3629, the UTF-8 spec:193* https://www.ietf.org/rfc/rfc3629.txt194*195* Char. number range | UTF-8 octet sequence196* (hexadecimal) | (binary)197* --------------------+---------------------------------------------198* 0000 0000-0000 007F | 0xxxxxxx199* 0000 0080-0000 07FF | 110xxxxx 10xxxxxx200* 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx201* 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx202*/203204const Uint8 *str = (const Uint8 *) *_str;205const Uint32 octet = (Uint32) (slen ? *str : 0);206207if (octet == 0) { // null terminator, end of string.208return 0; // don't advance `*_str`.209} else if ((octet & 0x80) == 0) { // 0xxxxxxx: one byte codepoint.210(*_str)++;211return octet;212} else if (((octet & 0xE0) == 0xC0) && (slen >= 2)) { // 110xxxxx 10xxxxxx: two byte codepoint.213const Uint8 str1 = str[1];214if ((str1 & 0xC0) == 0x80) { // If trailing bytes aren't 10xxxxxx, sequence is bogus.215const Uint32 result = ((octet & 0x1F) << 6) | (str1 & 0x3F);216if (result >= 0x0080) { // rfc3629 says you can't use overlong sequences for smaller values.217*_str += 2;218return result;219}220}221} else if (((octet & 0xF0) == 0xE0) && (slen >= 3)) { // 1110xxxx 10xxxxxx 10xxxxxx: three byte codepoint.222const Uint8 str1 = str[1];223const Uint8 str2 = str[2];224if (((str1 & 0xC0) == 0x80) && ((str2 & 0xC0) == 0x80)) { // If trailing bytes aren't 10xxxxxx, sequence is bogus.225const Uint32 octet2 = ((Uint32) (str1 & 0x3F)) << 6;226const Uint32 octet3 = ((Uint32) (str2 & 0x3F));227const Uint32 result = ((octet & 0x0F) << 12) | octet2 | octet3;228if (result >= 0x800) { // rfc3629 says you can't use overlong sequences for smaller values.229if ((result < 0xD800) || (result > 0xDFFF)) { // UTF-16 surrogate values are illegal in UTF-8.230*_str += 3;231return result;232}233}234}235} else if (((octet & 0xF8) == 0xF0) && (slen >= 4)) { // 11110xxxx 10xxxxxx 10xxxxxx 10xxxxxx: four byte codepoint.236const Uint8 str1 = str[1];237const Uint8 str2 = str[2];238const Uint8 str3 = str[3];239if (((str1 & 0xC0) == 0x80) && ((str2 & 0xC0) == 0x80) && ((str3 & 0xC0) == 0x80)) { // If trailing bytes aren't 10xxxxxx, sequence is bogus.240const Uint32 octet2 = ((Uint32) (str1 & 0x1F)) << 12;241const Uint32 octet3 = ((Uint32) (str2 & 0x3F)) << 6;242const Uint32 octet4 = ((Uint32) (str3 & 0x3F));243const Uint32 result = ((octet & 0x07) << 18) | octet2 | octet3 | octet4;244if (result >= 0x10000) { // rfc3629 says you can't use overlong sequences for smaller values.245*_str += 4;246return result;247}248}249}250251// bogus byte, skip ahead, return a REPLACEMENT CHARACTER.252(*_str)++;253return SDL_INVALID_UNICODE_CODEPOINT;254}255256Uint32 SDL_StepUTF8(const char **pstr, size_t *pslen)257{258if (!pslen) {259return StepUTF8(pstr, 4); // 4 == max codepoint size.260}261const char *origstr = *pstr;262const Uint32 result = StepUTF8(pstr, *pslen);263*pslen -= (size_t) (*pstr - origstr);264return result;265}266267Uint32 SDL_StepBackUTF8(const char *start, const char **pstr)268{269if (!pstr || *pstr <= start) {270return 0;271}272273// Step back over the previous UTF-8 character274const char *str = *pstr;275do {276if (str == start) {277break;278}279--str;280} while ((*str & 0xC0) == 0x80);281282size_t length = (*pstr - str);283*pstr = str;284return StepUTF8(&str, length);285}286287#if (SDL_SIZEOF_WCHAR_T == 2)288static Uint32 StepUTF16(const Uint16 **_str, const size_t slen)289{290const Uint16 *str = *_str;291Uint32 cp = (Uint32) *(str++);292if (cp == 0) {293return 0; // don't advance string pointer.294} else if ((cp >= 0xDC00) && (cp <= 0xDFFF)) {295cp = SDL_INVALID_UNICODE_CODEPOINT; // Orphaned second half of surrogate pair296} else if ((cp >= 0xD800) && (cp <= 0xDBFF)) { // start of surrogate pair!297const Uint32 pair = (Uint32) *str;298if ((pair == 0) || ((pair < 0xDC00) || (pair > 0xDFFF))) {299cp = SDL_INVALID_UNICODE_CODEPOINT;300} else {301str++; // eat the other surrogate.302cp = 0x10000 + (((cp - 0xD800) << 10) | (pair - 0xDC00));303}304}305306*_str = str;307return (cp > 0x10FFFF) ? SDL_INVALID_UNICODE_CODEPOINT : cp;308}309#elif (SDL_SIZEOF_WCHAR_T == 4)310static Uint32 StepUTF32(const Uint32 **_str, const size_t slen)311{312if (!slen) {313return 0;314}315316const Uint32 *str = *_str;317const Uint32 cp = *str;318if (cp == 0) {319return 0; // don't advance string pointer.320}321322(*_str)++;323return (cp > 0x10FFFF) ? SDL_INVALID_UNICODE_CODEPOINT : cp;324}325#endif326327#define UTF8_IsLeadByte(c) ((c) >= 0xC0 && (c) <= 0xF4)328#define UTF8_IsTrailingByte(c) ((c) >= 0x80 && (c) <= 0xBF)329330static size_t UTF8_GetTrailingBytes(unsigned char c)331{332if (c >= 0xC0 && c <= 0xDF) {333return 1;334} else if (c >= 0xE0 && c <= 0xEF) {335return 2;336} else if (c >= 0xF0 && c <= 0xF4) {337return 3;338}339340return 0;341}342343#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOL) || !defined(HAVE_STRTOUL) || !defined(HAVE_STRTOLL) || !defined(HAVE_STRTOULL) || !defined(HAVE_STRTOD)344/**345* Parses an unsigned long long and returns the unsigned value and sign bit.346*347* Positive values are clamped to ULLONG_MAX.348* The result `value == 0 && negative` indicates negative overflow349* and might need to be handled differently depending on whether a350* signed or unsigned integer is being parsed.351*/352static size_t SDL_ScanUnsignedLongLongInternal(const char *text, int count, int radix, unsigned long long *valuep, bool *negativep)353{354const unsigned long long ullong_max = ~0ULL;355356const char *text_start = text;357const char *number_start = text_start;358unsigned long long value = 0;359bool negative = false;360bool overflow = false;361362if (radix == 0 || (radix >= 2 && radix <= 36)) {363while (SDL_isspace(*text)) {364++text;365}366if (*text == '-' || *text == '+') {367negative = *text == '-';368++text;369}370if ((radix == 0 || radix == 16) && *text == '0' && (text[1] == 'x' || text[1] == 'X')) {371text += 2;372radix = 16;373} else if (radix == 0 && *text == '0' && (text[1] >= '0' && text[1] <= '9')) {374++text;375radix = 8;376} else if (radix == 0) {377radix = 10;378}379number_start = text;380do {381unsigned long long digit;382if (*text >= '0' && *text <= '9') {383digit = *text - '0';384} else if (radix > 10) {385if (*text >= 'A' && *text < 'A' + (radix - 10)) {386digit = 10 + (*text - 'A');387} else if (*text >= 'a' && *text < 'a' + (radix - 10)) {388digit = 10 + (*text - 'a');389} else {390break;391}392} else {393break;394}395if (value != 0 && radix > ullong_max / value) {396overflow = true;397} else {398value *= radix;399if (digit > ullong_max - value) {400overflow = true;401} else {402value += digit;403}404}405++text;406} while (count == 0 || (text - text_start) != count);407}408if (text == number_start) {409if (radix == 16 && text > text_start && (*(text - 1) == 'x' || *(text - 1) == 'X')) {410// the string was "0x"; consume the '0' but not the 'x'411--text;412} else {413// no number was parsed, and thus no characters were consumed414text = text_start;415}416}417if (overflow) {418if (negative) {419value = 0;420} else {421value = ullong_max;422}423} else if (value == 0) {424negative = false;425}426*valuep = value;427*negativep = negative;428return text - text_start;429}430#endif431432#ifndef HAVE_WCSTOL433// SDL_ScanUnsignedLongLongInternalW assumes that wchar_t can be converted to int without truncating bits434SDL_COMPILE_TIME_ASSERT(wchar_t_int, sizeof(wchar_t) <= sizeof(int));435436/**437* Parses an unsigned long long and returns the unsigned value and sign bit.438*439* Positive values are clamped to ULLONG_MAX.440* The result `value == 0 && negative` indicates negative overflow441* and might need to be handled differently depending on whether a442* signed or unsigned integer is being parsed.443*/444static size_t SDL_ScanUnsignedLongLongInternalW(const wchar_t *text, int count, int radix, unsigned long long *valuep, bool *negativep)445{446const unsigned long long ullong_max = ~0ULL;447448const wchar_t *text_start = text;449const wchar_t *number_start = text_start;450unsigned long long value = 0;451bool negative = false;452bool overflow = false;453454if (radix == 0 || (radix >= 2 && radix <= 36)) {455while (SDL_isspace(*text)) {456++text;457}458if (*text == '-' || *text == '+') {459negative = *text == '-';460++text;461}462if ((radix == 0 || radix == 16) && *text == '0' && (text[1] == 'x' || text[1] == 'X')) {463text += 2;464radix = 16;465} else if (radix == 0 && *text == '0' && (text[1] >= '0' && text[1] <= '9')) {466++text;467radix = 8;468} else if (radix == 0) {469radix = 10;470}471number_start = text;472do {473unsigned long long digit;474if (*text >= '0' && *text <= '9') {475digit = *text - '0';476} else if (radix > 10) {477if (*text >= 'A' && *text < 'A' + (radix - 10)) {478digit = 10 + (*text - 'A');479} else if (*text >= 'a' && *text < 'a' + (radix - 10)) {480digit = 10 + (*text - 'a');481} else {482break;483}484} else {485break;486}487if (value != 0 && radix > ullong_max / value) {488overflow = true;489} else {490value *= radix;491if (digit > ullong_max - value) {492overflow = true;493} else {494value += digit;495}496}497++text;498} while (count == 0 || (text - text_start) != count);499}500if (text == number_start) {501if (radix == 16 && text > text_start && (*(text - 1) == 'x' || *(text - 1) == 'X')) {502// the string was "0x"; consume the '0' but not the 'x'503--text;504} else {505// no number was parsed, and thus no characters were consumed506text = text_start;507}508}509if (overflow) {510if (negative) {511value = 0;512} else {513value = ullong_max;514}515} else if (value == 0) {516negative = false;517}518*valuep = value;519*negativep = negative;520return text - text_start;521}522#endif523524#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOL)525static size_t SDL_ScanLong(const char *text, int count, int radix, long *valuep)526{527const unsigned long long_max = (~0UL) >> 1;528unsigned long long value;529bool negative;530size_t len = SDL_ScanUnsignedLongLongInternal(text, count, radix, &value, &negative);531if (negative) {532const unsigned long abs_long_min = long_max + 1;533if (value == 0 || value > abs_long_min) {534value = 0ULL - abs_long_min;535} else {536value = 0ULL - value;537}538} else if (value > long_max) {539value = long_max;540}541*valuep = (long)value;542return len;543}544#endif545546#ifndef HAVE_WCSTOL547static size_t SDL_ScanLongW(const wchar_t *text, int count, int radix, long *valuep)548{549const unsigned long long_max = (~0UL) >> 1;550unsigned long long value;551bool negative;552size_t len = SDL_ScanUnsignedLongLongInternalW(text, count, radix, &value, &negative);553if (negative) {554const unsigned long abs_long_min = long_max + 1;555if (value == 0 || value > abs_long_min) {556value = 0ULL - abs_long_min;557} else {558value = 0ULL - value;559}560} else if (value > long_max) {561value = long_max;562}563*valuep = (long)value;564return len;565}566#endif567568#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOUL)569static size_t SDL_ScanUnsignedLong(const char *text, int count, int radix, unsigned long *valuep)570{571const unsigned long ulong_max = ~0UL;572unsigned long long value;573bool negative;574size_t len = SDL_ScanUnsignedLongLongInternal(text, count, radix, &value, &negative);575if (negative) {576if (value == 0 || value > ulong_max) {577value = ulong_max;578} else if (value == ulong_max) {579value = 1;580} else {581value = 0ULL - value;582}583} else if (value > ulong_max) {584value = ulong_max;585}586*valuep = (unsigned long)value;587return len;588}589#endif590591#ifndef HAVE_VSSCANF592static size_t SDL_ScanUintPtrT(const char *text, uintptr_t *valuep)593{594const uintptr_t uintptr_max = ~(uintptr_t)0;595unsigned long long value;596bool negative;597size_t len = SDL_ScanUnsignedLongLongInternal(text, 0, 16, &value, &negative);598if (negative) {599if (value == 0 || value > uintptr_max) {600value = uintptr_max;601} else if (value == uintptr_max) {602value = 1;603} else {604value = 0ULL - value;605}606} else if (value > uintptr_max) {607value = uintptr_max;608}609*valuep = (uintptr_t)value;610return len;611}612#endif613614#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOLL)615static size_t SDL_ScanLongLong(const char *text, int count, int radix, long long *valuep)616{617const unsigned long long llong_max = (~0ULL) >> 1;618unsigned long long value;619bool negative;620size_t len = SDL_ScanUnsignedLongLongInternal(text, count, radix, &value, &negative);621if (negative) {622const unsigned long long abs_llong_min = llong_max + 1;623if (value == 0 || value > abs_llong_min) {624value = 0ULL - abs_llong_min;625} else {626value = 0ULL - value;627}628} else if (value > llong_max) {629value = llong_max;630}631*valuep = value;632return len;633}634#endif635636#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOULL) || !defined(HAVE_STRTOD)637static size_t SDL_ScanUnsignedLongLong(const char *text, int count, int radix, unsigned long long *valuep)638{639const unsigned long long ullong_max = ~0ULL;640bool negative;641size_t len = SDL_ScanUnsignedLongLongInternal(text, count, radix, valuep, &negative);642if (negative) {643if (*valuep == 0) {644*valuep = ullong_max;645} else {646*valuep = 0ULL - *valuep;647}648}649return len;650}651#endif652653#if !defined(HAVE_VSSCANF) || !defined(HAVE_STRTOD)654static size_t SDL_ScanFloat(const char *text, double *valuep)655{656const char *text_start = text;657const char *number_start = text_start;658double value = 0.0;659bool negative = false;660661while (SDL_isspace(*text)) {662++text;663}664if (*text == '-' || *text == '+') {665negative = *text == '-';666++text;667}668number_start = text;669if (SDL_isdigit(*text)) {670value += SDL_strtoull(text, (char **)(&text), 10);671if (*text == '.') {672double denom = 10;673++text;674while (SDL_isdigit(*text)) {675value += (double)(*text - '0') / denom;676denom *= 10;677++text;678}679}680}681if (text == number_start) {682// no number was parsed, and thus no characters were consumed683text = text_start;684} else if (negative) {685value = -value;686}687*valuep = value;688return text - text_start;689}690#endif691692int SDL_memcmp(const void *s1, const void *s2, size_t len)693{694#ifdef SDL_PLATFORM_VITA695/*696Using memcmp on NULL is UB per POSIX / C99 7.21.1/2.697But, both linux and bsd allow that, with an exception:698zero length strings are always identical, so NULLs are never dereferenced.699sceClibMemcmp on PSVita doesn't allow that, so we check ourselves.700*/701if (len == 0) {702return 0;703}704return sceClibMemcmp(s1, s2, len);705#elif defined(HAVE_MEMCMP)706return memcmp(s1, s2, len);707#else708char *s1p = (char *)s1;709char *s2p = (char *)s2;710while (len--) {711if (*s1p != *s2p) {712return *s1p - *s2p;713}714++s1p;715++s2p;716}717return 0;718#endif // HAVE_MEMCMP719}720721size_t SDL_strlen(const char *string)722{723#ifdef HAVE_STRLEN724return strlen(string);725#else726size_t len = 0;727while (*string++) {728++len;729}730return len;731#endif // HAVE_STRLEN732}733734size_t SDL_strnlen(const char *string, size_t maxlen)735{736#ifdef HAVE_STRNLEN737return strnlen(string, maxlen);738#else739size_t len = 0;740while (len < maxlen && *string++) {741++len;742}743return len;744#endif // HAVE_STRNLEN745}746747size_t SDL_wcslen(const wchar_t *string)748{749#ifdef HAVE_WCSLEN750return wcslen(string);751#else752size_t len = 0;753while (*string++) {754++len;755}756return len;757#endif // HAVE_WCSLEN758}759760size_t SDL_wcsnlen(const wchar_t *string, size_t maxlen)761{762#ifdef HAVE_WCSNLEN763return wcsnlen(string, maxlen);764#else765size_t len = 0;766while (len < maxlen && *string++) {767++len;768}769return len;770#endif // HAVE_WCSNLEN771}772773size_t SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen)774{775#ifdef HAVE_WCSLCPY776return wcslcpy(dst, src, maxlen);777#else778size_t srclen = SDL_wcslen(src);779if (maxlen > 0) {780size_t len = SDL_min(srclen, maxlen - 1);781SDL_memcpy(dst, src, len * sizeof(wchar_t));782dst[len] = '\0';783}784return srclen;785#endif // HAVE_WCSLCPY786}787788size_t SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen)789{790#ifdef HAVE_WCSLCAT791return wcslcat(dst, src, maxlen);792#else793size_t dstlen = SDL_wcslen(dst);794size_t srclen = SDL_wcslen(src);795if (dstlen < maxlen) {796SDL_wcslcpy(dst + dstlen, src, maxlen - dstlen);797}798return dstlen + srclen;799#endif // HAVE_WCSLCAT800}801802wchar_t *SDL_wcsdup(const wchar_t *string)803{804size_t len = ((SDL_wcslen(string) + 1) * sizeof(wchar_t));805wchar_t *newstr = (wchar_t *)SDL_malloc(len);806if (newstr) {807SDL_memcpy(newstr, string, len);808}809return newstr;810}811812wchar_t *SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen)813{814size_t length = SDL_wcslen(needle);815if (length == 0) {816return (wchar_t *)haystack;817}818while (maxlen >= length && *haystack) {819if (maxlen >= length && SDL_wcsncmp(haystack, needle, length) == 0) {820return (wchar_t *)haystack;821}822++haystack;823--maxlen;824}825return NULL;826}827828wchar_t *SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle)829{830#ifdef HAVE_WCSSTR831return SDL_const_cast(wchar_t *, wcsstr(haystack, needle));832#else833return SDL_wcsnstr(haystack, needle, SDL_wcslen(haystack));834#endif // HAVE_WCSSTR835}836837int SDL_wcscmp(const wchar_t *str1, const wchar_t *str2)838{839#ifdef HAVE_WCSCMP840return wcscmp(str1, str2);841#else842while (*str1 && *str2) {843if (*str1 != *str2) {844break;845}846++str1;847++str2;848}849return *str1 - *str2;850#endif // HAVE_WCSCMP851}852853int SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen)854{855#ifdef HAVE_WCSNCMP856return wcsncmp(str1, str2, maxlen);857#else858while (*str1 && *str2 && maxlen) {859if (*str1 != *str2) {860break;861}862++str1;863++str2;864--maxlen;865}866if (!maxlen) {867return 0;868}869return *str1 - *str2;870871#endif // HAVE_WCSNCMP872}873874int SDL_wcscasecmp(const wchar_t *wstr1, const wchar_t *wstr2)875{876#if (SDL_SIZEOF_WCHAR_T == 2)877const Uint16 *str1 = (const Uint16 *) wstr1;878const Uint16 *str2 = (const Uint16 *) wstr2;879UNICODE_STRCASECMP(16, 2, 2, (void) str1start, (void) str2start); // always NULL-terminated, no need to adjust lengths.880#elif (SDL_SIZEOF_WCHAR_T == 4)881const Uint32 *str1 = (const Uint32 *) wstr1;882const Uint32 *str2 = (const Uint32 *) wstr2;883UNICODE_STRCASECMP(32, 1, 1, (void) str1start, (void) str2start); // always NULL-terminated, no need to adjust lengths.884#else885#error Unexpected wchar_t size886return -1;887#endif888}889890int SDL_wcsncasecmp(const wchar_t *wstr1, const wchar_t *wstr2, size_t maxlen)891{892size_t slen1 = maxlen;893size_t slen2 = maxlen;894895#if (SDL_SIZEOF_WCHAR_T == 2)896const Uint16 *str1 = (const Uint16 *) wstr1;897const Uint16 *str2 = (const Uint16 *) wstr2;898UNICODE_STRCASECMP(16, slen1, slen2, slen1 -= (size_t) (str1 - str1start), slen2 -= (size_t) (str2 - str2start));899#elif (SDL_SIZEOF_WCHAR_T == 4)900const Uint32 *str1 = (const Uint32 *) wstr1;901const Uint32 *str2 = (const Uint32 *) wstr2;902UNICODE_STRCASECMP(32, slen1, slen2, slen1 -= (size_t) (str1 - str1start), slen2 -= (size_t) (str2 - str2start));903#else904#error Unexpected wchar_t size905return -1;906#endif907}908909long SDL_wcstol(const wchar_t *string, wchar_t **endp, int base)910{911#ifdef HAVE_WCSTOL912return wcstol(string, endp, base);913#else914long value = 0;915size_t len = SDL_ScanLongW(string, 0, base, &value);916if (endp) {917*endp = (wchar_t *)string + len;918}919return value;920#endif // HAVE_WCSTOL921}922923size_t SDL_strlcpy(SDL_OUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen)924{925#ifdef HAVE_STRLCPY926return strlcpy(dst, src, maxlen);927#else928size_t srclen = SDL_strlen(src);929if (maxlen > 0) {930size_t len = SDL_min(srclen, maxlen - 1);931SDL_memcpy(dst, src, len);932dst[len] = '\0';933}934return srclen;935#endif // HAVE_STRLCPY936}937938size_t SDL_utf8strlcpy(SDL_OUT_Z_CAP(dst_bytes) char *dst, const char *src, size_t dst_bytes)939{940size_t bytes = 0;941942if (dst_bytes > 0) {943size_t src_bytes = SDL_strlen(src);944size_t i = 0;945size_t trailing_bytes = 0;946947bytes = SDL_min(src_bytes, dst_bytes - 1);948if (bytes) {949unsigned char c = (unsigned char)src[bytes - 1];950if (UTF8_IsLeadByte(c)) {951--bytes;952} else if (UTF8_IsTrailingByte(c)) {953for (i = bytes - 1; i != 0; --i) {954c = (unsigned char)src[i];955trailing_bytes = UTF8_GetTrailingBytes(c);956if (trailing_bytes) {957if ((bytes - i) != (trailing_bytes + 1)) {958bytes = i;959}960961break;962}963}964}965SDL_memcpy(dst, src, bytes);966}967dst[bytes] = '\0';968}969970return bytes;971}972973size_t SDL_utf8strlen(const char *str)974{975size_t result = 0;976while (SDL_StepUTF8(&str, NULL)) {977result++;978}979return result;980}981982size_t SDL_utf8strnlen(const char *str, size_t bytes)983{984size_t result = 0;985while (SDL_StepUTF8(&str, &bytes)) {986result++;987}988return result;989}990991size_t SDL_strlcat(SDL_INOUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen)992{993#ifdef HAVE_STRLCAT994return strlcat(dst, src, maxlen);995#else996size_t dstlen = SDL_strlen(dst);997size_t srclen = SDL_strlen(src);998if (dstlen < maxlen) {999SDL_strlcpy(dst + dstlen, src, maxlen - dstlen);1000}1001return dstlen + srclen;1002#endif // HAVE_STRLCAT1003}10041005char *SDL_strdup(const char *string)1006{1007size_t len = SDL_strlen(string) + 1;1008char *newstr = (char *)SDL_malloc(len);1009if (newstr) {1010SDL_memcpy(newstr, string, len);1011}1012return newstr;1013}10141015char *SDL_strndup(const char *string, size_t maxlen)1016{1017size_t len = SDL_strnlen(string, maxlen);1018char *newstr = (char *)SDL_malloc(len + 1);1019if (newstr) {1020SDL_memcpy(newstr, string, len);1021newstr[len] = '\0';1022}1023return newstr;1024}10251026char *SDL_strrev(char *string)1027{1028#ifdef HAVE__STRREV1029return _strrev(string);1030#else1031size_t len = SDL_strlen(string);1032char *a = &string[0];1033char *b = &string[len - 1];1034len /= 2;1035while (len--) {1036const char c = *a; // NOLINT(clang-analyzer-core.uninitialized.Assign)1037*a++ = *b;1038*b-- = c;1039}1040return string;1041#endif // HAVE__STRREV1042}10431044char *SDL_strupr(char *string)1045{1046char *bufp = string;1047while (*bufp) {1048*bufp = (char)SDL_toupper((unsigned char)*bufp);1049++bufp;1050}1051return string;1052}10531054char *SDL_strlwr(char *string)1055{1056char *bufp = string;1057while (*bufp) {1058*bufp = (char)SDL_tolower((unsigned char)*bufp);1059++bufp;1060}1061return string;1062}10631064char *SDL_strchr(const char *string, int c)1065{1066#ifdef HAVE_STRCHR1067return SDL_const_cast(char *, strchr(string, c));1068#elif defined(HAVE_INDEX)1069return SDL_const_cast(char *, index(string, c));1070#else1071while (*string) {1072if (*string == c) {1073return (char *)string;1074}1075++string;1076}1077if (c == '\0') {1078return (char *)string;1079}1080return NULL;1081#endif // HAVE_STRCHR1082}10831084char *SDL_strrchr(const char *string, int c)1085{1086#ifdef HAVE_STRRCHR1087return SDL_const_cast(char *, strrchr(string, c));1088#elif defined(HAVE_RINDEX)1089return SDL_const_cast(char *, rindex(string, c));1090#else1091const char *bufp = string + SDL_strlen(string);1092while (bufp >= string) {1093if (*bufp == c) {1094return (char *)bufp;1095}1096--bufp;1097}1098return NULL;1099#endif // HAVE_STRRCHR1100}11011102char *SDL_strnstr(const char *haystack, const char *needle, size_t maxlen)1103{1104#ifdef HAVE_STRNSTR1105return SDL_const_cast(char *, strnstr(haystack, needle, maxlen));1106#else1107size_t length = SDL_strlen(needle);1108if (length == 0) {1109return (char *)haystack;1110}1111while (maxlen >= length && *haystack) {1112if (SDL_strncmp(haystack, needle, length) == 0) {1113return (char *)haystack;1114}1115++haystack;1116--maxlen;1117}1118return NULL;1119#endif // HAVE_STRSTR1120}11211122char *SDL_strstr(const char *haystack, const char *needle)1123{1124#ifdef HAVE_STRSTR1125return SDL_const_cast(char *, strstr(haystack, needle));1126#else1127return SDL_strnstr(haystack, needle, SDL_strlen(haystack));1128#endif // HAVE_STRSTR1129}11301131char *SDL_strcasestr(const char *haystack, const char *needle)1132{1133const size_t length = SDL_strlen(needle);1134do {1135if (SDL_strncasecmp(haystack, needle, length) == 0) {1136return (char *)haystack;1137}1138} while (SDL_StepUTF8(&haystack, NULL)); // move ahead by a full codepoint at a time, regardless of bytes.11391140return NULL;1141}11421143#if !defined(HAVE__LTOA) || !defined(HAVE__I64TOA) || \1144!defined(HAVE__ULTOA) || !defined(HAVE__UI64TOA)1145static const char ntoa_table[] = {1146'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',1147'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',1148'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',1149'U', 'V', 'W', 'X', 'Y', 'Z'1150};1151#endif // ntoa() conversion table11521153char *SDL_itoa(int value, char *string, int radix)1154{1155#ifdef HAVE_ITOA1156return itoa(value, string, radix);1157#else1158return SDL_ltoa((long)value, string, radix);1159#endif // HAVE_ITOA1160}11611162char *SDL_uitoa(unsigned int value, char *string, int radix)1163{1164#ifdef HAVE__UITOA1165return _uitoa(value, string, radix);1166#else1167return SDL_ultoa((unsigned long)value, string, radix);1168#endif // HAVE__UITOA1169}11701171char *SDL_ltoa(long value, char *string, int radix)1172{1173#ifdef HAVE__LTOA1174return _ltoa(value, string, radix);1175#else1176char *bufp = string;11771178if (value < 0) {1179*bufp++ = '-';1180SDL_ultoa(-value, bufp, radix);1181} else {1182SDL_ultoa(value, bufp, radix);1183}11841185return string;1186#endif // HAVE__LTOA1187}11881189char *SDL_ultoa(unsigned long value, char *string, int radix)1190{1191#ifdef HAVE__ULTOA1192return _ultoa(value, string, radix);1193#else1194char *bufp = string;11951196if (value) {1197while (value > 0) {1198*bufp++ = ntoa_table[value % radix];1199value /= radix;1200}1201} else {1202*bufp++ = '0';1203}1204*bufp = '\0';12051206// The numbers went into the string backwards. :)1207SDL_strrev(string);12081209return string;1210#endif // HAVE__ULTOA1211}12121213char *SDL_lltoa(long long value, char *string, int radix)1214{1215#ifdef HAVE__I64TOA1216return _i64toa(value, string, radix);1217#else1218char *bufp = string;12191220if (value < 0) {1221*bufp++ = '-';1222SDL_ulltoa(-value, bufp, radix);1223} else {1224SDL_ulltoa(value, bufp, radix);1225}12261227return string;1228#endif // HAVE__I64TOA1229}12301231char *SDL_ulltoa(unsigned long long value, char *string, int radix)1232{1233#ifdef HAVE__UI64TOA1234return _ui64toa(value, string, radix);1235#else1236char *bufp = string;12371238if (value) {1239while (value > 0) {1240*bufp++ = ntoa_table[value % radix];1241value /= radix;1242}1243} else {1244*bufp++ = '0';1245}1246*bufp = '\0';12471248// The numbers went into the string backwards. :)1249SDL_strrev(string);12501251return string;1252#endif // HAVE__UI64TOA1253}12541255int SDL_atoi(const char *string)1256{1257#ifdef HAVE_ATOI1258return atoi(string);1259#else1260return SDL_strtol(string, NULL, 10);1261#endif // HAVE_ATOI1262}12631264double SDL_atof(const char *string)1265{1266#ifdef HAVE_ATOF1267return atof(string);1268#else1269return SDL_strtod(string, NULL);1270#endif // HAVE_ATOF1271}12721273long SDL_strtol(const char *string, char **endp, int base)1274{1275#ifdef HAVE_STRTOL1276return strtol(string, endp, base);1277#else1278long value = 0;1279size_t len = SDL_ScanLong(string, 0, base, &value);1280if (endp) {1281*endp = (char *)string + len;1282}1283return value;1284#endif // HAVE_STRTOL1285}12861287unsigned long SDL_strtoul(const char *string, char **endp, int base)1288{1289#ifdef HAVE_STRTOUL1290return strtoul(string, endp, base);1291#else1292unsigned long value = 0;1293size_t len = SDL_ScanUnsignedLong(string, 0, base, &value);1294if (endp) {1295*endp = (char *)string + len;1296}1297return value;1298#endif // HAVE_STRTOUL1299}13001301long long SDL_strtoll(const char *string, char **endp, int base)1302{1303#ifdef HAVE_STRTOLL1304return strtoll(string, endp, base);1305#else1306long long value = 0;1307size_t len = SDL_ScanLongLong(string, 0, base, &value);1308if (endp) {1309*endp = (char *)string + len;1310}1311return value;1312#endif // HAVE_STRTOLL1313}13141315unsigned long long SDL_strtoull(const char *string, char **endp, int base)1316{1317#ifdef HAVE_STRTOULL1318return strtoull(string, endp, base);1319#else1320unsigned long long value = 0;1321size_t len = SDL_ScanUnsignedLongLong(string, 0, base, &value);1322if (endp) {1323*endp = (char *)string + len;1324}1325return value;1326#endif // HAVE_STRTOULL1327}13281329double SDL_strtod(const char *string, char **endp)1330{1331#ifdef HAVE_STRTOD1332return strtod(string, endp);1333#else1334double value;1335size_t len = SDL_ScanFloat(string, &value);1336if (endp) {1337*endp = (char *)string + len;1338}1339return value;1340#endif // HAVE_STRTOD1341}13421343int SDL_strcmp(const char *str1, const char *str2)1344{1345#ifdef HAVE_STRCMP1346return strcmp(str1, str2);1347#else1348int result;13491350while (1) {1351result = ((unsigned char)*str1 - (unsigned char)*str2);1352if (result != 0 || (*str1 == '\0' /* && *str2 == '\0'*/)) {1353break;1354}1355++str1;1356++str2;1357}1358return result;1359#endif // HAVE_STRCMP1360}13611362int SDL_strncmp(const char *str1, const char *str2, size_t maxlen)1363{1364#ifdef HAVE_STRNCMP1365return strncmp(str1, str2, maxlen);1366#else1367int result = 0;13681369while (maxlen) {1370result = (int)(unsigned char)*str1 - (unsigned char)*str2;1371if (result != 0 || *str1 == '\0' /* && *str2 == '\0'*/) {1372break;1373}1374++str1;1375++str2;1376--maxlen;1377}1378return result;1379#endif // HAVE_STRNCMP1380}13811382int SDL_strcasecmp(const char *str1, const char *str2)1383{1384UNICODE_STRCASECMP(8, 4, 4, (void) str1start, (void) str2start); // always NULL-terminated, no need to adjust lengths.1385}13861387int SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen)1388{1389size_t slen1 = maxlen;1390size_t slen2 = maxlen;1391UNICODE_STRCASECMP(8, slen1, slen2, slen1 -= (size_t) (str1 - ((const char *) str1start)), slen2 -= (size_t) (str2 - ((const char *) str2start)));1392}13931394int SDL_sscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, ...)1395{1396int rc;1397va_list ap;1398va_start(ap, fmt);1399rc = SDL_vsscanf(text, fmt, ap);1400va_end(ap);1401return rc;1402}14031404#ifdef HAVE_VSSCANF1405int SDL_vsscanf(const char *text, const char *fmt, va_list ap)1406{1407return vsscanf(text, fmt, ap);1408}1409#else1410static bool CharacterMatchesSet(char c, const char *set, size_t set_len)1411{1412bool invert = false;1413bool result = false;14141415if (*set == '^') {1416invert = true;1417++set;1418--set_len;1419}1420while (set_len > 0 && !result) {1421if (set_len >= 3 && set[1] == '-') {1422char low_char = SDL_min(set[0], set[2]);1423char high_char = SDL_max(set[0], set[2]);1424if (c >= low_char && c <= high_char) {1425result = true;1426}1427set += 3;1428set_len -= 3;1429} else {1430if (c == *set) {1431result = true;1432}1433++set;1434--set_len;1435}1436}1437if (invert) {1438result = !result;1439}1440return result;1441}14421443// NOLINTNEXTLINE(readability-non-const-parameter)1444int SDL_vsscanf(const char *text, SDL_SCANF_FORMAT_STRING const char *fmt, va_list ap)1445{1446const char *start = text;1447int result = 0;14481449if (!text || !*text) {1450return -1;1451}14521453while (*fmt) {1454if (*fmt == ' ') {1455while (SDL_isspace((unsigned char)*text)) {1456++text;1457}1458++fmt;1459continue;1460}1461if (*fmt == '%') {1462bool done = false;1463long count = 0;1464int radix = 10;1465enum1466{1467DO_SHORT,1468DO_INT,1469DO_LONG,1470DO_LONGLONG,1471DO_SIZE_T1472} inttype = DO_INT;1473size_t advance;1474bool suppress = false;14751476++fmt;1477if (*fmt == '%') {1478if (*text == '%') {1479++text;1480++fmt;1481continue;1482}1483break;1484}1485if (*fmt == '*') {1486suppress = true;1487++fmt;1488}1489fmt += SDL_ScanLong(fmt, 0, 10, &count);14901491if (*fmt == 'c') {1492if (!count) {1493count = 1;1494}1495if (suppress) {1496while (count--) {1497++text;1498}1499} else {1500char *valuep = va_arg(ap, char *);1501while (count--) {1502*valuep++ = *text++;1503}1504++result;1505}1506continue;1507}15081509while (SDL_isspace((unsigned char)*text)) {1510++text;1511}15121513// FIXME: implement more of the format specifiers1514while (!done) {1515switch (*fmt) {1516case '*':1517suppress = true;1518break;1519case 'h':1520if (inttype == DO_INT) {1521inttype = DO_SHORT;1522} else if (inttype > DO_SHORT) {1523++inttype;1524}1525break;1526case 'l':1527if (inttype < DO_LONGLONG) {1528++inttype;1529}1530break;1531case 'I':1532if (SDL_strncmp(fmt, "I64", 3) == 0) {1533fmt += 2;1534inttype = DO_LONGLONG;1535}1536break;1537case 'z':1538inttype = DO_SIZE_T;1539break;1540case 'i':1541{1542int index = 0;1543if (text[index] == '-') {1544++index;1545}1546if (text[index] == '0') {1547if (SDL_tolower((unsigned char)text[index + 1]) == 'x') {1548radix = 16;1549} else {1550radix = 8;1551}1552}1553}1554SDL_FALLTHROUGH;1555case 'd':1556if (inttype == DO_LONGLONG) {1557long long value = 0;1558advance = SDL_ScanLongLong(text, count, radix, &value);1559text += advance;1560if (advance && !suppress) {1561Sint64 *valuep = va_arg(ap, Sint64 *);1562*valuep = value;1563++result;1564}1565} else if (inttype == DO_SIZE_T) {1566long long value = 0;1567advance = SDL_ScanLongLong(text, count, radix, &value);1568text += advance;1569if (advance && !suppress) {1570size_t *valuep = va_arg(ap, size_t *);1571*valuep = (size_t)value;1572++result;1573}1574} else {1575long value = 0;1576advance = SDL_ScanLong(text, count, radix, &value);1577text += advance;1578if (advance && !suppress) {1579switch (inttype) {1580case DO_SHORT:1581{1582short *valuep = va_arg(ap, short *);1583*valuep = (short)value;1584} break;1585case DO_INT:1586{1587int *valuep = va_arg(ap, int *);1588*valuep = (int)value;1589} break;1590case DO_LONG:1591{1592long *valuep = va_arg(ap, long *);1593*valuep = value;1594} break;1595case DO_LONGLONG:1596case DO_SIZE_T:1597// Handled above1598break;1599}1600++result;1601}1602}1603done = true;1604break;1605case 'o':1606if (radix == 10) {1607radix = 8;1608}1609SDL_FALLTHROUGH;1610case 'x':1611case 'X':1612if (radix == 10) {1613radix = 16;1614}1615SDL_FALLTHROUGH;1616case 'u':1617if (inttype == DO_LONGLONG) {1618unsigned long long value = 0;1619advance = SDL_ScanUnsignedLongLong(text, count, radix, &value);1620text += advance;1621if (advance && !suppress) {1622Uint64 *valuep = va_arg(ap, Uint64 *);1623*valuep = value;1624++result;1625}1626} else if (inttype == DO_SIZE_T) {1627unsigned long long value = 0;1628advance = SDL_ScanUnsignedLongLong(text, count, radix, &value);1629text += advance;1630if (advance && !suppress) {1631size_t *valuep = va_arg(ap, size_t *);1632*valuep = (size_t)value;1633++result;1634}1635} else {1636unsigned long value = 0;1637advance = SDL_ScanUnsignedLong(text, count, radix, &value);1638text += advance;1639if (advance && !suppress) {1640switch (inttype) {1641case DO_SHORT:1642{1643short *valuep = va_arg(ap, short *);1644*valuep = (short)value;1645} break;1646case DO_INT:1647{1648int *valuep = va_arg(ap, int *);1649*valuep = (int)value;1650} break;1651case DO_LONG:1652{1653long *valuep = va_arg(ap, long *);1654*valuep = value;1655} break;1656case DO_LONGLONG:1657case DO_SIZE_T:1658// Handled above1659break;1660}1661++result;1662}1663}1664done = true;1665break;1666case 'p':1667{1668uintptr_t value = 0;1669advance = SDL_ScanUintPtrT(text, &value);1670text += advance;1671if (advance && !suppress) {1672void **valuep = va_arg(ap, void **);1673*valuep = (void *)value;1674++result;1675}1676}1677done = true;1678break;1679case 'f':1680{1681double value = 0.0;1682advance = SDL_ScanFloat(text, &value);1683text += advance;1684if (advance && !suppress) {1685float *valuep = va_arg(ap, float *);1686*valuep = (float)value;1687++result;1688}1689}1690done = true;1691break;1692case 's':1693if (suppress) {1694while (!SDL_isspace((unsigned char)*text)) {1695++text;1696if (count) {1697if (--count == 0) {1698break;1699}1700}1701}1702} else {1703char *valuep = va_arg(ap, char *);1704while (!SDL_isspace((unsigned char)*text)) {1705*valuep++ = *text++;1706if (count) {1707if (--count == 0) {1708break;1709}1710}1711}1712*valuep = '\0';1713++result;1714}1715done = true;1716break;1717case 'n':1718switch (inttype) {1719case DO_SHORT:1720{1721short *valuep = va_arg(ap, short *);1722*valuep = (short)(text - start);1723} break;1724case DO_INT:1725{1726int *valuep = va_arg(ap, int *);1727*valuep = (int)(text - start);1728} break;1729case DO_LONG:1730{1731long *valuep = va_arg(ap, long *);1732*valuep = (long)(text - start);1733} break;1734case DO_LONGLONG:1735{1736long long *valuep = va_arg(ap, long long *);1737*valuep = (long long)(text - start);1738} break;1739case DO_SIZE_T:1740{1741size_t *valuep = va_arg(ap, size_t *);1742*valuep = (size_t)(text - start);1743} break;1744}1745done = true;1746break;1747case '[':1748{1749const char *set = fmt + 1;1750while (*fmt && *fmt != ']') {1751++fmt;1752}1753if (*fmt) {1754size_t set_len = (fmt - set);1755if (suppress) {1756while (CharacterMatchesSet(*text, set, set_len)) {1757++text;1758if (count) {1759if (--count == 0) {1760break;1761}1762}1763}1764} else {1765bool had_match = false;1766char *valuep = va_arg(ap, char *);1767while (CharacterMatchesSet(*text, set, set_len)) {1768had_match = true;1769*valuep++ = *text++;1770if (count) {1771if (--count == 0) {1772break;1773}1774}1775}1776*valuep = '\0';1777if (had_match) {1778++result;1779}1780}1781}1782}1783done = true;1784break;1785default:1786done = true;1787break;1788}1789++fmt;1790}1791continue;1792}1793if (*text == *fmt) {1794++text;1795++fmt;1796continue;1797}1798// Text didn't match format specifier1799break;1800}18011802return result;1803}1804#endif // HAVE_VSSCANF18051806int SDL_snprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)1807{1808va_list ap;1809int result;18101811va_start(ap, fmt);1812result = SDL_vsnprintf(text, maxlen, fmt, ap);1813va_end(ap);18141815return result;1816}18171818int SDL_swprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const wchar_t *fmt, ...)1819{1820va_list ap;1821int result;18221823va_start(ap, fmt);1824result = SDL_vswprintf(text, maxlen, fmt, ap);1825va_end(ap);18261827return result;1828}18291830#if defined(HAVE_LIBC) && defined(__WATCOMC__)1831// _vsnprintf() doesn't ensure nul termination1832int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *fmt, va_list ap)1833{1834int result;1835if (!fmt) {1836fmt = "";1837}1838result = _vsnprintf(text, maxlen, fmt, ap);1839if (maxlen > 0) {1840text[maxlen - 1] = '\0';1841}1842if (result < 0) {1843result = (int)maxlen;1844}1845return result;1846}1847#elif defined(HAVE_VSNPRINTF)1848int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, const char *fmt, va_list ap)1849{1850if (!fmt) {1851fmt = "";1852}1853return vsnprintf(text, maxlen, fmt, ap);1854}1855#else1856#define TEXT_AND_LEN_ARGS (length < maxlen) ? &text[length] : NULL, (length < maxlen) ? (maxlen - length) : 018571858// FIXME: implement more of the format specifiers1859typedef enum1860{1861SDL_CASE_NOCHANGE,1862SDL_CASE_LOWER,1863SDL_CASE_UPPER1864} SDL_letter_case;18651866typedef struct1867{1868bool left_justify;1869bool force_sign;1870bool force_type; // for now: used only by float printer, ignored otherwise.1871bool pad_zeroes;1872SDL_letter_case force_case;1873int width;1874int radix;1875int precision;1876} SDL_FormatInfo;18771878static size_t SDL_PrintString(char *text, size_t maxlen, SDL_FormatInfo *info, const char *string)1879{1880const char fill = (info && info->pad_zeroes) ? '0' : ' ';1881size_t width = 0;1882size_t filllen = 0;1883size_t length = 0;1884size_t slen, sz;18851886if (!string) {1887string = "(null)";1888}18891890sz = SDL_strlen(string);1891if (info && info->width > 0 && (size_t)info->width > sz) {1892width = info->width - sz;1893if (info->precision >= 0 && (size_t)info->precision < sz) {1894width += sz - (size_t)info->precision;1895}18961897filllen = SDL_min(width, maxlen);1898if (!info->left_justify) {1899SDL_memset(text, fill, filllen);1900text += filllen;1901maxlen -= filllen;1902length += width;1903filllen = 0;1904}1905}19061907SDL_strlcpy(text, string, maxlen);1908length += sz;19091910if (filllen > 0) {1911SDL_memset(text + sz, fill, filllen);1912length += width;1913}19141915if (info) {1916if (info->precision >= 0 && (size_t)info->precision < sz) {1917slen = (size_t)info->precision;1918if (slen < maxlen) {1919text[slen] = '\0';1920}1921length -= (sz - slen);1922}1923if (maxlen > 1) {1924if (info->force_case == SDL_CASE_LOWER) {1925SDL_strlwr(text);1926} else if (info->force_case == SDL_CASE_UPPER) {1927SDL_strupr(text);1928}1929}1930}1931return length;1932}19331934static size_t SDL_PrintStringW(char *text, size_t maxlen, SDL_FormatInfo *info, const wchar_t *wide_string)1935{1936size_t length = 0;1937if (wide_string) {1938char *string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)(wide_string), (SDL_wcslen(wide_string) + 1) * sizeof(*wide_string));1939length = SDL_PrintString(TEXT_AND_LEN_ARGS, info, string);1940SDL_free(string);1941} else {1942length = SDL_PrintString(TEXT_AND_LEN_ARGS, info, NULL);1943}1944return length;1945}19461947static void SDL_IntPrecisionAdjust(char *num, size_t maxlen, SDL_FormatInfo *info)1948{ // left-pad num with zeroes.1949size_t sz, pad, have_sign;19501951if (!info) {1952return;1953}19541955have_sign = 0;1956if (*num == '-' || *num == '+') {1957have_sign = 1;1958++num;1959--maxlen;1960}1961sz = SDL_strlen(num);1962if (info->precision > 0 && sz < (size_t)info->precision) {1963pad = (size_t)info->precision - sz;1964if (pad + sz + 1 <= maxlen) { // otherwise ignore the precision1965SDL_memmove(num + pad, num, sz + 1);1966SDL_memset(num, '0', pad);1967}1968}1969info->precision = -1; // so that SDL_PrintString() doesn't make a mess.19701971if (info->pad_zeroes && info->width > 0 && (size_t)info->width > sz + have_sign) {1972/* handle here: spaces are added before the sign1973but zeroes must be placed _after_ the sign. */1974// sz hasn't changed: we ignore pad_zeroes if a precision is given.1975pad = (size_t)info->width - sz - have_sign;1976if (pad + sz + 1 <= maxlen) {1977SDL_memmove(num + pad, num, sz + 1);1978SDL_memset(num, '0', pad);1979}1980info->width = 0; // so that SDL_PrintString() doesn't make a mess.1981}1982}19831984static size_t SDL_PrintLong(char *text, size_t maxlen, SDL_FormatInfo *info, long value)1985{1986char num[130], *p = num;19871988if (info->force_sign && value >= 0L) {1989*p++ = '+';1990}19911992SDL_ltoa(value, p, info ? info->radix : 10);1993SDL_IntPrecisionAdjust(num, sizeof(num), info);1994return SDL_PrintString(text, maxlen, info, num);1995}19961997static size_t SDL_PrintUnsignedLong(char *text, size_t maxlen, SDL_FormatInfo *info, unsigned long value)1998{1999char num[130];20002001SDL_ultoa(value, num, info ? info->radix : 10);2002SDL_IntPrecisionAdjust(num, sizeof(num), info);2003return SDL_PrintString(text, maxlen, info, num);2004}20052006static size_t SDL_PrintLongLong(char *text, size_t maxlen, SDL_FormatInfo *info, long long value)2007{2008char num[130], *p = num;20092010if (info->force_sign && value >= (Sint64)0) {2011*p++ = '+';2012}20132014SDL_lltoa(value, p, info ? info->radix : 10);2015SDL_IntPrecisionAdjust(num, sizeof(num), info);2016return SDL_PrintString(text, maxlen, info, num);2017}20182019static size_t SDL_PrintUnsignedLongLong(char *text, size_t maxlen, SDL_FormatInfo *info, unsigned long long value)2020{2021char num[130];20222023SDL_ulltoa(value, num, info ? info->radix : 10);2024SDL_IntPrecisionAdjust(num, sizeof(num), info);2025return SDL_PrintString(text, maxlen, info, num);2026}20272028static size_t SDL_PrintFloat(char *text, size_t maxlen, SDL_FormatInfo *info, double arg, bool g)2029{2030char num[327];2031size_t length = 0;2032size_t integer_length;2033int precision = info->precision;20342035// This isn't especially accurate, but hey, it's easy. :)2036unsigned long long value;20372038if (arg < 0.0 || (arg == 0.0 && 1.0 / arg < 0.0)) { // additional check for signed zero2039num[length++] = '-';2040arg = -arg;2041} else if (info->force_sign) {2042num[length++] = '+';2043}2044value = (unsigned long long)arg;2045integer_length = SDL_PrintUnsignedLongLong(&num[length], sizeof(num) - length, NULL, value);2046length += integer_length;2047arg -= value;2048if (precision < 0) {2049precision = 6;2050}2051if (g) {2052// The precision includes the integer portion2053precision -= SDL_min((int)integer_length, precision);2054}2055if (info->force_type || precision > 0) {2056const char decimal_separator = '.';2057double integer_value;20582059SDL_assert(length < sizeof(num));2060num[length++] = decimal_separator;2061while (precision > 1) {2062arg *= 10.0;2063arg = SDL_modf(arg, &integer_value);2064SDL_assert(length < sizeof(num));2065num[length++] = '0' + (char)integer_value;2066--precision;2067}2068if (precision == 1) {2069arg *= 10.0;2070integer_value = SDL_round(arg);2071if (integer_value == 10.0) {2072// Carry the one...2073size_t i;20742075for (i = length; i--; ) {2076if (num[i] == decimal_separator) {2077continue;2078}2079if (num[i] == '9') {2080num[i] = '0';2081if (i == 0 || num[i - 1] == '-' || num[i - 1] == '+') {2082SDL_memmove(&num[i+1], &num[i], length - i);2083num[i] = '1';2084++length;2085break;2086}2087} else {2088++num[i];2089break;2090}2091}2092SDL_assert(length < sizeof(num));2093num[length++] = '0';2094} else {2095SDL_assert(length < sizeof(num));2096num[length++] = '0' + (char)integer_value;2097}2098}20992100if (g) {2101// Trim trailing zeroes and decimal separator2102size_t i;21032104for (i = length - 1; num[i] != decimal_separator; --i) {2105if (num[i] == '0') {2106--length;2107} else {2108break;2109}2110}2111if (num[i] == decimal_separator) {2112--length;2113}2114}2115}2116num[length] = '\0';21172118info->precision = -1;2119length = SDL_PrintString(text, maxlen, info, num);21202121if (info->width > 0 && (size_t)info->width > length) {2122const char fill = info->pad_zeroes ? '0' : ' ';2123size_t width = info->width - length;2124size_t filllen, movelen;21252126filllen = SDL_min(width, maxlen);2127movelen = SDL_min(length, (maxlen - filllen));2128SDL_memmove(&text[filllen], text, movelen);2129SDL_memset(text, fill, filllen);2130length += width;2131}2132return length;2133}21342135static size_t SDL_PrintPointer(char *text, size_t maxlen, SDL_FormatInfo *info, const void *value)2136{2137char num[130];2138size_t length;21392140if (!value) {2141return SDL_PrintString(text, maxlen, info, NULL);2142}21432144SDL_ulltoa((unsigned long long)(uintptr_t)value, num, 16);2145length = SDL_PrintString(text, maxlen, info, "0x");2146return length + SDL_PrintString(TEXT_AND_LEN_ARGS, info, num);2147}21482149// NOLINTNEXTLINE(readability-non-const-parameter)2150int SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)2151{2152size_t length = 0;21532154if (!text) {2155maxlen = 0;2156}2157if (!fmt) {2158fmt = "";2159}2160while (*fmt) {2161if (*fmt == '%') {2162bool done = false;2163bool check_flag;2164SDL_FormatInfo info;2165enum2166{2167DO_INT,2168DO_LONG,2169DO_LONGLONG,2170DO_SIZE_T2171} inttype = DO_INT;21722173SDL_zero(info);2174info.radix = 10;2175info.precision = -1;21762177check_flag = true;2178while (check_flag) {2179++fmt;2180switch (*fmt) {2181case '-':2182info.left_justify = true;2183break;2184case '+':2185info.force_sign = true;2186break;2187case '#':2188info.force_type = true;2189break;2190case '0':2191info.pad_zeroes = true;2192break;2193default:2194check_flag = false;2195break;2196}2197}21982199if (*fmt >= '0' && *fmt <= '9') {2200info.width = SDL_strtol(fmt, (char **)&fmt, 0);2201} else if (*fmt == '*') {2202++fmt;2203info.width = va_arg(ap, int);2204}22052206if (*fmt == '.') {2207++fmt;2208if (*fmt >= '0' && *fmt <= '9') {2209info.precision = SDL_strtol(fmt, (char **)&fmt, 0);2210} else if (*fmt == '*') {2211++fmt;2212info.precision = va_arg(ap, int);2213} else {2214info.precision = 0;2215}2216if (info.precision < 0) {2217info.precision = 0;2218}2219}22202221while (!done) {2222switch (*fmt) {2223case '%':2224if (length < maxlen) {2225text[length] = '%';2226}2227++length;2228done = true;2229break;2230case 'c':2231// char is promoted to int when passed through (...)2232if (length < maxlen) {2233text[length] = (char)va_arg(ap, int);2234}2235++length;2236done = true;2237break;2238case 'h':2239// short is promoted to int when passed through (...)2240break;2241case 'l':2242if (inttype < DO_LONGLONG) {2243++inttype;2244}2245break;2246case 'I':2247if (SDL_strncmp(fmt, "I64", 3) == 0) {2248fmt += 2;2249inttype = DO_LONGLONG;2250}2251break;2252case 'z':2253inttype = DO_SIZE_T;2254break;2255case 'i':2256case 'd':2257if (info.precision >= 0) {2258info.pad_zeroes = false;2259}2260switch (inttype) {2261case DO_INT:2262length += SDL_PrintLong(TEXT_AND_LEN_ARGS, &info,2263(long)va_arg(ap, int));2264break;2265case DO_LONG:2266length += SDL_PrintLong(TEXT_AND_LEN_ARGS, &info,2267va_arg(ap, long));2268break;2269case DO_LONGLONG:2270length += SDL_PrintLongLong(TEXT_AND_LEN_ARGS, &info,2271va_arg(ap, long long));2272break;2273case DO_SIZE_T:2274length += SDL_PrintLongLong(TEXT_AND_LEN_ARGS, &info,2275va_arg(ap, size_t));2276break;2277}2278done = true;2279break;2280case 'p':2281info.force_case = SDL_CASE_LOWER;2282length += SDL_PrintPointer(TEXT_AND_LEN_ARGS, &info, va_arg(ap, void *));2283done = true;2284break;2285case 'x':2286info.force_case = SDL_CASE_LOWER;2287SDL_FALLTHROUGH;2288case 'X':2289if (info.force_case == SDL_CASE_NOCHANGE) {2290info.force_case = SDL_CASE_UPPER;2291}2292if (info.radix == 10) {2293info.radix = 16;2294}2295SDL_FALLTHROUGH;2296case 'o':2297if (info.radix == 10) {2298info.radix = 8;2299}2300SDL_FALLTHROUGH;2301case 'u':2302info.force_sign = false;2303if (info.precision >= 0) {2304info.pad_zeroes = false;2305}2306switch (inttype) {2307case DO_INT:2308length += SDL_PrintUnsignedLong(TEXT_AND_LEN_ARGS, &info,2309va_arg(ap, unsigned int));2310break;2311case DO_LONG:2312length += SDL_PrintUnsignedLong(TEXT_AND_LEN_ARGS, &info,2313va_arg(ap, unsigned long));2314break;2315case DO_LONGLONG:2316length += SDL_PrintUnsignedLongLong(TEXT_AND_LEN_ARGS, &info,2317va_arg(ap, unsigned long long));2318break;2319case DO_SIZE_T:2320length += SDL_PrintUnsignedLongLong(TEXT_AND_LEN_ARGS, &info,2321va_arg(ap, size_t));2322break;2323}2324done = true;2325break;2326case 'f':2327length += SDL_PrintFloat(TEXT_AND_LEN_ARGS, &info, va_arg(ap, double), false);2328done = true;2329break;2330case 'g':2331length += SDL_PrintFloat(TEXT_AND_LEN_ARGS, &info, va_arg(ap, double), true);2332done = true;2333break;2334case 'S':2335info.pad_zeroes = false;2336length += SDL_PrintStringW(TEXT_AND_LEN_ARGS, &info, va_arg(ap, wchar_t *));2337done = true;2338break;2339case 's':2340info.pad_zeroes = false;2341if (inttype > DO_INT) {2342length += SDL_PrintStringW(TEXT_AND_LEN_ARGS, &info, va_arg(ap, wchar_t *));2343} else {2344length += SDL_PrintString(TEXT_AND_LEN_ARGS, &info, va_arg(ap, char *));2345}2346done = true;2347break;2348default:2349done = true;2350break;2351}2352++fmt;2353}2354} else {2355if (length < maxlen) {2356text[length] = *fmt;2357}2358++fmt;2359++length;2360}2361}2362if (length < maxlen) {2363text[length] = '\0';2364} else if (maxlen > 0) {2365text[maxlen - 1] = '\0';2366}2367return (int)length;2368}23692370#undef TEXT_AND_LEN_ARGS2371#endif // HAVE_VSNPRINTF23722373int SDL_vswprintf(SDL_OUT_Z_CAP(maxlen) wchar_t *text, size_t maxlen, const wchar_t *fmt, va_list ap)2374{2375char *fmt_utf8 = NULL;2376if (fmt) {2377fmt_utf8 = SDL_iconv_string("UTF-8", "WCHAR_T", (const char *)fmt, (SDL_wcslen(fmt) + 1) * sizeof(wchar_t));2378if (!fmt_utf8) {2379return -1;2380}2381}23822383char tinybuf[64]; // for really small strings, calculate it once.23842385// generate the text to find the final text length2386va_list aq;2387va_copy(aq, ap);2388const int utf8len = SDL_vsnprintf(tinybuf, sizeof (tinybuf), fmt_utf8, aq);2389va_end(aq);23902391if (utf8len < 0) {2392SDL_free(fmt_utf8);2393return -1;2394}23952396bool isstack = false;2397char *smallbuf = NULL;2398char *utf8buf;2399int result;24002401if (utf8len < sizeof (tinybuf)) { // whole thing fit in the stack buffer, just use that copy.2402utf8buf = tinybuf;2403} else { // didn't fit in the stack buffer, allocate the needed space and run it again.2404utf8buf = smallbuf = SDL_small_alloc(char, utf8len + 1, &isstack);2405if (!smallbuf) {2406SDL_free(fmt_utf8);2407return -1; // oh well.2408}2409const int utf8len2 = SDL_vsnprintf(smallbuf, utf8len + 1, fmt_utf8, ap);2410if (utf8len2 > utf8len) {2411SDL_free(fmt_utf8);2412return SDL_SetError("Formatted output changed between two runs"); // race condition on the parameters, and we no longer have room...yikes.2413}2414}24152416SDL_free(fmt_utf8);24172418wchar_t *wbuf = (wchar_t *)SDL_iconv_string("WCHAR_T", "UTF-8", utf8buf, utf8len + 1);2419if (wbuf) {2420if (text) {2421SDL_wcslcpy(text, wbuf, maxlen);2422}2423result = (int)SDL_wcslen(wbuf);2424SDL_free(wbuf);2425} else {2426result = -1;2427}24282429if (smallbuf != NULL) {2430SDL_small_free(smallbuf, isstack);2431}24322433return result;2434}24352436int SDL_asprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)2437{2438va_list ap;2439int result;24402441va_start(ap, fmt);2442result = SDL_vasprintf(strp, fmt, ap);2443va_end(ap);24442445return result;2446}24472448int SDL_vasprintf(char **strp, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)2449{2450int result;2451int size = 100; // Guess we need no more than 100 bytes2452char *p, *np;2453va_list aq;24542455*strp = NULL;24562457p = (char *)SDL_malloc(size);2458if (!p) {2459return -1;2460}24612462while (1) {2463// Try to print in the allocated space2464va_copy(aq, ap);2465result = SDL_vsnprintf(p, size, fmt, aq);2466va_end(aq);24672468// Check error code2469if (result < 0) {2470SDL_free(p);2471return result;2472}24732474// If that worked, return the string2475if (result < size) {2476*strp = p;2477return result;2478}24792480// Else try again with more space2481size = result + 1; // Precisely what is needed24822483np = (char *)SDL_realloc(p, size);2484if (!np) {2485SDL_free(p);2486return -1;2487} else {2488p = np;2489}2490}2491}24922493char * SDL_strpbrk(const char *str, const char *breakset)2494{2495#ifdef HAVE_STRPBRK2496return strpbrk(str, breakset);2497#else24982499for (; *str; str++) {2500const char *b;25012502for (b = breakset; *b; b++) {2503if (*str == *b) {2504return (char *) str;2505}2506}2507}2508return NULL;2509#endif2510}251125122513