714 lines
16 KiB
C++
Executable File
714 lines
16 KiB
C++
Executable File
// vim: set ts=4 sw=4 tw=99 noet:
|
|
//
|
|
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
|
|
// Copyright (C) The AMX Mod X Development Team.
|
|
//
|
|
// This software is licensed under the GNU General Public License, version 3 or higher.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://alliedmods.net/amxmodx-license
|
|
|
|
#include <time.h>
|
|
#include "amxmodx.h"
|
|
|
|
#if ( defined(__linux__) || defined(__APPLE__) ) && !defined _vsnprintf
|
|
#define _vsnprintf vsnprintf
|
|
#endif
|
|
|
|
char *UTIL_VarArgs(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
static char string[4096];
|
|
|
|
va_start(ap, fmt);
|
|
_vsnprintf(string, sizeof(string)-1, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return string;
|
|
}
|
|
|
|
int UTIL_ReadFlags(const char* c)
|
|
{
|
|
int flags = 0;
|
|
|
|
while (*c)
|
|
flags |= (1<<(*c++ - 'a'));
|
|
|
|
return flags;
|
|
}
|
|
|
|
void UTIL_GetFlags(char* f, int a)
|
|
{
|
|
for (int i = 'a'; i <= 'z'; ++i)
|
|
{
|
|
if (a & 1) *f++ = i;
|
|
a >>= 1;
|
|
}
|
|
|
|
*f = 0;
|
|
}
|
|
|
|
/* warning - don't pass here const string */
|
|
void UTIL_ShowMenu(edict_t* pEdict, int slots, int time, char *menu, int mlen)
|
|
{
|
|
char *n = menu;
|
|
char c = 0;
|
|
int a;
|
|
|
|
if (!gmsgShowMenu)
|
|
return; // some games don't support ShowMenu (Firearms)
|
|
|
|
do
|
|
{
|
|
a = mlen;
|
|
if (a > 175) a = 175;
|
|
mlen -= a;
|
|
c = *(n+=a);
|
|
*n = 0;
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgShowMenu, NULL, pEdict);
|
|
WRITE_SHORT(slots);
|
|
WRITE_CHAR(time);
|
|
WRITE_BYTE(c ? TRUE : FALSE);
|
|
WRITE_STRING(menu);
|
|
MESSAGE_END();
|
|
*n = c;
|
|
menu = n;
|
|
}
|
|
while (*n);
|
|
}
|
|
|
|
/* warning - don't pass here const string */
|
|
void UTIL_ShowMOTD(edict_t *client, char *motd, int mlen, const char *name)
|
|
{
|
|
if (!gmsgMOTD)
|
|
return; // :TODO: Maybe output a warning log?
|
|
|
|
if (gmsgServerName)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgServerName, NULL, client);
|
|
WRITE_STRING(name);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
char *n = motd;
|
|
char c = 0;
|
|
int a;
|
|
|
|
while (*n)
|
|
{
|
|
a = mlen;
|
|
if (a > 175) a = 175;
|
|
mlen -= a;
|
|
c = *(n += a);
|
|
*n = 0;
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgMOTD, NULL, client);
|
|
WRITE_BYTE(c ? FALSE : TRUE);
|
|
WRITE_STRING(motd);
|
|
MESSAGE_END();
|
|
*n = c;
|
|
motd = n;
|
|
}
|
|
|
|
if (gmsgServerName)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgServerName, NULL, client);
|
|
WRITE_STRING(hostname->string);
|
|
MESSAGE_END();
|
|
}
|
|
}
|
|
|
|
void UTIL_IntToString(int value, char *output)
|
|
{
|
|
static const char *words[] =
|
|
{"zero ","one ","two ","three ","four ",
|
|
"five ", "six ","seven ","eight ","nine ","ten ",
|
|
"eleven ","twelve ","thirteen ","fourteen ","fifteen ",
|
|
"sixteen ","seventeen ","eighteen ","nineteen ",
|
|
"twenty ","thirty ","fourty ", "fifty ","sixty ",
|
|
"seventy ","eighty ","ninety ",
|
|
"hundred ","thousand "};
|
|
|
|
*output = 0;
|
|
if (value < 0) value = -value;
|
|
int tho = value / 1000;
|
|
int aaa = 0;
|
|
|
|
if (tho)
|
|
{
|
|
aaa += sprintf(&output[aaa], "%s", words[tho]);
|
|
aaa += sprintf(&output[aaa], "%s", words[29]);
|
|
value = value % 1000;
|
|
}
|
|
|
|
int hun = value / 100;
|
|
|
|
if (hun)
|
|
{
|
|
aaa += sprintf(&output[aaa], "%s", words[hun]);
|
|
aaa += sprintf(&output[aaa], "%s", words[28]);
|
|
value = value % 100;
|
|
}
|
|
|
|
int ten = value / 10;
|
|
int unit = value % 10;
|
|
|
|
if (ten)
|
|
aaa += sprintf(&output[aaa], "%s", words[(ten > 1) ? (ten + 18) : (unit + 10)]);
|
|
|
|
if (ten != 1 && (unit || (!value && !hun && !tho)))
|
|
sprintf(&output[aaa], "%s", words[unit]);
|
|
}
|
|
|
|
char* UTIL_SplitHudMessage(const char *src)
|
|
{
|
|
static char message[512];
|
|
short b = 0, d = 0, e = 0, c = -1;
|
|
|
|
while (src[d] && e < 480)
|
|
{
|
|
if (src[d] == ' ')
|
|
{
|
|
c = e;
|
|
}
|
|
else if (src[d] == '\n')
|
|
{
|
|
c = -1;
|
|
b = 0;
|
|
}
|
|
|
|
message[e++] = src[d++];
|
|
|
|
if (++b == 69)
|
|
{
|
|
if (c == -1)
|
|
{
|
|
message[e++] = '\n';
|
|
b = 0;
|
|
} else {
|
|
message[c] = '\n';
|
|
b = e - c - 1;
|
|
c = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
message[e] = 0;
|
|
return message;
|
|
}
|
|
|
|
unsigned short FixedUnsigned16(float value, float scale)
|
|
{
|
|
int output = (int)(value * scale);
|
|
|
|
if (output < 0)
|
|
output = 0;
|
|
else if (output > 0xFFFF)
|
|
output = 0xFFFF;
|
|
|
|
return (unsigned short)output;
|
|
}
|
|
|
|
short FixedSigned16(float value, float scale)
|
|
{
|
|
int output = (int)(value * scale);
|
|
|
|
if (output > 32767)
|
|
output = 32767;
|
|
else if (output < -32768)
|
|
output = -32768;
|
|
|
|
return (short)output;
|
|
}
|
|
|
|
void UTIL_HudMessage(edict_t *pEntity, const hudtextparms_t &textparms, const char *pMessage)
|
|
{
|
|
if (pEntity)
|
|
MESSAGE_BEGIN(MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pEntity);
|
|
else
|
|
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
|
|
|
|
WRITE_BYTE(29);
|
|
WRITE_BYTE(textparms.channel & 0xFF);
|
|
WRITE_SHORT(FixedSigned16(textparms.x, (1<<13)));
|
|
WRITE_SHORT(FixedSigned16(textparms.y, (1<<13)));
|
|
WRITE_BYTE(textparms.effect);
|
|
WRITE_BYTE(textparms.r1);
|
|
WRITE_BYTE(textparms.g1);
|
|
WRITE_BYTE(textparms.b1);
|
|
WRITE_BYTE(0);
|
|
WRITE_BYTE(255);
|
|
WRITE_BYTE(255);
|
|
WRITE_BYTE(250);
|
|
WRITE_BYTE(0);
|
|
WRITE_SHORT(FixedUnsigned16(textparms.fadeinTime, (1<<8)));
|
|
WRITE_SHORT(FixedUnsigned16(textparms.fadeoutTime, (1<<8)));
|
|
WRITE_SHORT(FixedUnsigned16(textparms.holdTime, (1<<8)));
|
|
|
|
if (textparms.effect == 2)
|
|
WRITE_SHORT(FixedUnsigned16(textparms.fxTime, (1<<8)));
|
|
|
|
WRITE_STRING(pMessage);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void UTIL_DHudMessage(edict_t *pEntity, const hudtextparms_t &textparms, const char *pMessage, unsigned int length)
|
|
{
|
|
#define DRC_CMD_MESSAGE 6
|
|
|
|
if (pEntity)
|
|
MESSAGE_BEGIN(MSG_ONE_UNRELIABLE, SVC_DIRECTOR, NULL, pEntity);
|
|
else
|
|
MESSAGE_BEGIN(MSG_BROADCAST, SVC_DIRECTOR);
|
|
|
|
WRITE_BYTE(length + 31); // message length (counting WRITE size)
|
|
WRITE_BYTE(DRC_CMD_MESSAGE);
|
|
WRITE_BYTE(textparms.effect);
|
|
WRITE_LONG(textparms.b1 + (textparms.g1 << 8) + (textparms.r1 << 16)); // pack color.
|
|
WRITE_LONG(amx_ftoc(textparms.x));
|
|
WRITE_LONG(amx_ftoc(textparms.y));
|
|
WRITE_LONG(amx_ftoc(textparms.fadeinTime));
|
|
WRITE_LONG(amx_ftoc(textparms.fadeoutTime));
|
|
WRITE_LONG(amx_ftoc(textparms.holdTime));
|
|
WRITE_LONG(amx_ftoc(textparms.fxTime));
|
|
WRITE_STRING(pMessage); // max length: 128. Truncated on the client.
|
|
MESSAGE_END();
|
|
}
|
|
|
|
/* warning - buffer of msg must be longer than 190 chars!
|
|
(here in AMX it is always longer) */
|
|
void UTIL_ClientPrint(edict_t *pEntity, int msg_dest, char *msg)
|
|
{
|
|
if (!gmsgTextMsg)
|
|
return; // :TODO: Maybe output a warning log?
|
|
|
|
char c = msg[190];
|
|
msg[190] = 0; // truncate without checking with strlen()
|
|
|
|
if (pEntity)
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgTextMsg, NULL, pEntity);
|
|
else
|
|
MESSAGE_BEGIN(MSG_BROADCAST, gmsgTextMsg);
|
|
|
|
WRITE_BYTE(msg_dest);
|
|
WRITE_STRING(msg);
|
|
MESSAGE_END();
|
|
msg[190] = c;
|
|
}
|
|
|
|
void UTIL_ClientSayText(edict_t *pEntity, int sender, char *msg)
|
|
{
|
|
if (!gmsgSayText)
|
|
return; // :TODO: Maybe output a warning log?
|
|
|
|
char c = msg[190];
|
|
msg[190] = 0; // truncate without checking with strlen()
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgSayText, NULL, pEntity);
|
|
WRITE_BYTE(sender);
|
|
WRITE_STRING(msg);
|
|
MESSAGE_END();
|
|
msg[190] = c;
|
|
}
|
|
|
|
void UTIL_TeamInfo(edict_t *pEntity, int playerIndex, const char *pszTeamName)
|
|
{
|
|
if (!gmsgTeamInfo)
|
|
return;
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgTeamInfo, NULL, pEntity);
|
|
WRITE_BYTE(playerIndex);
|
|
WRITE_STRING(pszTeamName);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
// UTIL_FakeClientCommand
|
|
// PURPOSE: Sends a fake client command to GameDLL
|
|
// HOW DOES IT WORK:
|
|
// 1) Stores command and arguments into a global and sets the global "fake" flag to true
|
|
// 2) Invokes ClientCommand in GameDLL
|
|
// 3) meta_api.cpp overrides Cmd_Args, Cmd_Argv, Cmd_Argc and gives them fake values if the "fake" flag is set
|
|
// 4) unsets the global "fake" flag
|
|
void UTIL_FakeClientCommand(edict_t *pEdict, const char *cmd, const char *arg1, const char *arg2, bool fwd)
|
|
{
|
|
if (!cmd)
|
|
return; // no command
|
|
|
|
// store command
|
|
g_fakecmd.argv[0] = cmd;
|
|
// if only arg2 is passed, swap the arguments
|
|
if (!arg1 && arg2)
|
|
{
|
|
arg1 = arg2;
|
|
arg2 = NULL;
|
|
}
|
|
|
|
// store arguments
|
|
if (arg2)
|
|
{ // both arguments passed
|
|
g_fakecmd.argc = 3; // 2 arguments + 1 command
|
|
// store arguments
|
|
g_fakecmd.argv[1] = arg1;
|
|
g_fakecmd.argv[2] = arg2;
|
|
// build argument line
|
|
UTIL_Format(g_fakecmd.args, 255, "%s %s", arg1, arg2);
|
|
// if UTIL_Format reached 255 chars limit, this will make sure there will be no access violation
|
|
g_fakecmd.args[255] = 0;
|
|
}
|
|
else if (arg1)
|
|
{ // only one argument passed
|
|
g_fakecmd.argc = 2; // 1 argument + 1 command
|
|
// store argument
|
|
g_fakecmd.argv[1] = arg1;
|
|
// build argument line
|
|
UTIL_Format(g_fakecmd.args, 255, "%s", arg1);
|
|
// if UTIL_Format reached 255 chars limit, this will make sure there will be no access violation
|
|
g_fakecmd.args[255] = 0;
|
|
}
|
|
else
|
|
g_fakecmd.argc = 1; // no argmuents -> only one command
|
|
|
|
/* Notify plugins about this command */
|
|
if (fwd)
|
|
{
|
|
/* Set flag so read_argc/v/s functions will give proper value */
|
|
g_fakecmd.notify = true;
|
|
|
|
if (executeForwards(FF_ClientCommand, static_cast<cell>(GET_PLAYER_POINTER(pEdict)->index)) > 0)
|
|
{
|
|
g_fakecmd.notify = false;
|
|
return;
|
|
}
|
|
|
|
/* check for command and if needed also for first argument and call proper function */
|
|
CmdMngr::iterator aa = g_commands.clcmdprefixbegin(cmd);
|
|
|
|
if (!aa)
|
|
{
|
|
aa = g_commands.clcmdbegin();
|
|
}
|
|
|
|
while (aa)
|
|
{
|
|
if ((*aa).matchCommandLine(cmd, arg1) && (*aa).getPlugin()->isExecutable((*aa).getFunction()))
|
|
{
|
|
if (executeForwards((*aa).getFunction(), static_cast<cell>(GET_PLAYER_POINTER(pEdict)->index)),
|
|
static_cast<cell>((*aa).getFlags()), static_cast<cell>((*aa).getId()) > 0)
|
|
{
|
|
g_fakecmd.notify = false;
|
|
return;
|
|
}
|
|
}
|
|
++aa;
|
|
}
|
|
|
|
/* Unset flag */
|
|
g_fakecmd.notify = false;
|
|
}
|
|
|
|
// set the global "fake" flag so the Cmd_Arg* functions will be superceded
|
|
g_fakecmd.fake = true;
|
|
// tell the GameDLL that the client sent a command
|
|
MDLL_ClientCommand(pEdict);
|
|
// unset the global "fake" flag
|
|
g_fakecmd.fake = false;
|
|
}
|
|
|
|
unsigned int UTIL_GetUTF8CharBytes(const char *stream)
|
|
{
|
|
unsigned char c = *(unsigned char *)stream;
|
|
if (c & (1 << 7))
|
|
{
|
|
if (c & (1 << 5))
|
|
{
|
|
if (c & (1 << 4))
|
|
{
|
|
return 4;
|
|
}
|
|
return 3;
|
|
}
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
template int UTIL_CheckValidChar<char>(char *);
|
|
template int UTIL_CheckValidChar<cell>(cell *);
|
|
|
|
template <typename D>
|
|
int UTIL_CheckValidChar(D *c)
|
|
{
|
|
int count;
|
|
int bytecount = 0;
|
|
|
|
for (count = 1; (*c & 0xC0) == 0x80; count++)
|
|
{
|
|
c--;
|
|
}
|
|
|
|
switch (*c & 0xF0)
|
|
{
|
|
case 0xC0:
|
|
case 0xD0:
|
|
{
|
|
bytecount = 2;
|
|
break;
|
|
}
|
|
case 0xE0:
|
|
{
|
|
bytecount = 3;
|
|
break;
|
|
}
|
|
case 0xF0:
|
|
{
|
|
bytecount = 4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bytecount != count)
|
|
{
|
|
return count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
|
|
{
|
|
size_t searchLen = strlen(search);
|
|
size_t replaceLen = strlen(replace);
|
|
|
|
char *ptr = subject;
|
|
unsigned int total = 0;
|
|
while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen, caseSensitive)) != NULL)
|
|
{
|
|
total++;
|
|
if (*ptr == '\0')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
template unsigned int strncopy<char, char>(char *, const char *src, size_t count);
|
|
template unsigned int strncopy<cell, char>(cell *, const char *src, size_t count);
|
|
template unsigned int strncopy<cell, cell>(cell *, const cell *src, size_t count);
|
|
|
|
template <typename D, typename S>
|
|
unsigned int strncopy(D *dest, const S *src, size_t count)
|
|
{
|
|
if (!count)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
D *start = dest;
|
|
|
|
while ((*src) && (--count))
|
|
{
|
|
*dest++ = *(unsigned char*)src++;
|
|
}
|
|
|
|
*dest = '\0';
|
|
|
|
return (dest - start);
|
|
}
|
|
|
|
/**
|
|
* NOTE: Do not edit this for the love of god unless you have
|
|
* read the test cases and understand the code behind each one.
|
|
* While I don't guarantee there aren't mistakes, I do guarantee
|
|
* that plugins will end up relying on tiny idiosyncrasies of this
|
|
* function, just like they did with AMX Mod X.
|
|
*
|
|
* There are explicitly more cases than the AMX Mod X version because
|
|
* we're not doing a blind copy. Each case is specifically optimized
|
|
* for what needs to be done. Even better, we don't have to error on
|
|
* bad buffer sizes. Instead, this function will smartly cut off the
|
|
* string in a way that pushes old data out.
|
|
*/
|
|
char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive)
|
|
{
|
|
char *ptr = subject;
|
|
size_t browsed = 0;
|
|
size_t textLen = strlen(subject);
|
|
|
|
/* It's not possible to search or replace */
|
|
if (searchLen > textLen)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Handle the case of one byte replacement.
|
|
* It's only valid in one case.
|
|
*/
|
|
if (maxLen == 1)
|
|
{
|
|
/* If the search matches and the replace length is 0,
|
|
* we can just terminate the string and be done.
|
|
*/
|
|
if ((caseSensitive ? strcmp(subject, search) : strcasecmp(subject, search)) == 0 && replaceLen == 0)
|
|
{
|
|
*subject = '\0';
|
|
return subject;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Subtract one off the maxlength so we can include the null terminator */
|
|
maxLen--;
|
|
|
|
while (*ptr != '\0' && (browsed <= textLen - searchLen))
|
|
{
|
|
/* See if we get a comparison */
|
|
if ((caseSensitive ? strncmp(ptr, search, searchLen) : strncasecmp(ptr, search, searchLen)) == 0)
|
|
{
|
|
if (replaceLen > searchLen)
|
|
{
|
|
/* First, see if we have enough space to do this operation */
|
|
if (maxLen - textLen < replaceLen - searchLen)
|
|
{
|
|
/* First, see if the replacement length goes out of bounds. */
|
|
if (browsed + replaceLen >= maxLen)
|
|
{
|
|
/* EXAMPLE CASE:
|
|
* Subject: AABBBCCC
|
|
* Buffer : 12 bytes
|
|
* Search : BBB
|
|
* Replace: DDDDDDDDDD
|
|
* OUTPUT : AADDDDDDDDD
|
|
* POSITION: ^
|
|
*/
|
|
/* If it does, we'll just bound the length and do a strcpy. */
|
|
replaceLen = maxLen - browsed;
|
|
|
|
/* Note, we add one to the final result for the null terminator */
|
|
strncopy(ptr, replace, replaceLen + 1);
|
|
|
|
/* Don't truncate a multi-byte character */
|
|
if (*(ptr + replaceLen - 1) & 1 << 7)
|
|
{
|
|
replaceLen -= UTIL_CheckValidChar(ptr + replaceLen - 1);
|
|
*(ptr + replaceLen) = '\0';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* EXAMPLE CASE:
|
|
* Subject: AABBBCCC
|
|
* Buffer : 12 bytes
|
|
* Search : BBB
|
|
* Replace: DDDDDDD
|
|
* OUTPUT : AADDDDDDDCC
|
|
* POSITION: ^
|
|
*/
|
|
/* We're going to have some bytes left over... */
|
|
size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
|
|
size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
|
|
char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
|
|
char *moveTo = ptr + replaceLen;
|
|
|
|
/* First, move our old data out of the way. */
|
|
memmove(moveTo, moveFrom, realBytesToCopy);
|
|
|
|
/* Now, do our replacement. */
|
|
memcpy(ptr, replace, replaceLen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* EXAMPLE CASE:
|
|
* Subject: AABBBCCC
|
|
* Buffer : 12 bytes
|
|
* Search : BBB
|
|
* Replace: DDDD
|
|
* OUTPUT : AADDDDCCC
|
|
* POSITION: ^
|
|
*/
|
|
/* Yes, we have enough space. Do a normal move operation. */
|
|
char *moveFrom = ptr + searchLen;
|
|
char *moveTo = ptr + replaceLen;
|
|
|
|
/* First move our old data out of the way. */
|
|
size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
|
|
memmove(moveTo, moveFrom, bytesToCopy);
|
|
|
|
/* Now do our replacement. */
|
|
memcpy(ptr, replace, replaceLen);
|
|
}
|
|
}
|
|
else if (replaceLen < searchLen)
|
|
{
|
|
/* EXAMPLE CASE:
|
|
* Subject: AABBBCCC
|
|
* Buffer : 12 bytes
|
|
* Search : BBB
|
|
* Replace: D
|
|
* OUTPUT : AADCCC
|
|
* POSITION: ^
|
|
*/
|
|
/* If the replacement does not grow the string length, we do not
|
|
* need to do any fancy checking at all. Yay!
|
|
*/
|
|
char *moveFrom = ptr + searchLen; /* Start after the search pointer */
|
|
char *moveTo = ptr + replaceLen; /* Copy to where the replacement ends */
|
|
|
|
/* Copy our replacement in, if any */
|
|
if (replaceLen)
|
|
{
|
|
memcpy(ptr, replace, replaceLen);
|
|
}
|
|
|
|
/* Figure out how many bytes to move down, including null terminator */
|
|
size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
|
|
|
|
/* Move the rest of the string down */
|
|
memmove(moveTo, moveFrom, bytesToCopy);
|
|
}
|
|
else
|
|
{
|
|
/* EXAMPLE CASE:
|
|
* Subject: AABBBCCC
|
|
* Buffer : 12 bytes
|
|
* Search : BBB
|
|
* Replace: DDD
|
|
* OUTPUT : AADDDCCC
|
|
* POSITION: ^
|
|
*/
|
|
/* We don't have to move anything around, just do a straight copy */
|
|
memcpy(ptr, replace, replaceLen);
|
|
}
|
|
|
|
return ptr + replaceLen;
|
|
}
|
|
ptr++;
|
|
browsed++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
size_t len = vsnprintf(buffer, maxlength, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (len >= maxlength)
|
|
{
|
|
buffer[maxlength - 1] = '\0';
|
|
return (maxlength - 1);
|
|
}
|
|
else
|
|
{
|
|
return len;
|
|
}
|
|
}
|