Files
2026-05-10 01:30:36 -07:00

2302 lines
46 KiB
C++

/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech Source Code 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.
IceTech Source Code 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 IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#ifdef USE_STRING_DATA_ALLOCATOR
static idDynamicBlockAlloc<char, 1 << 18, 128> stringDataAllocator;
#endif
idVec4 g_color_table[16] =
{
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
idVec4(1.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_RED
idVec4(0.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_GREEN
idVec4(1.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_YELLOW
idVec4(0.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_BLUE
idVec4(0.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_CYAN
idVec4(1.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_MAGENTA
idVec4(1.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_WHITE
idVec4(0.5f, 0.5f, 0.5f, 1.0f), // S_COLOR_GRAY
idVec4(0.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_BLACK
#ifdef QUAKE4
idVec4(0.94f, 0.62f, 0.05f, 1.0f), // S_COLOR_CONSOLE
#else
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
#endif
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
idVec4(0.0f, 0.0f, 0.0f, 1.0f),
};
const char* units[2][4] =
{
{ "B", "KB", "MB", "GB" },
{ "B/s", "KB/s", "MB/s", "GB/s" }
};
/*
============
idStr::ColorForIndex
============
*/
idVec4& idStr::ColorForIndex(int i) {
return g_color_table[i & 15];
}
/*
============
idStr::ReAllocate
============
*/
void idStr::ReAllocate(int amount, bool keepold) {
char* newbuffer;
int newsize;
int mod;
//assert( data );
assert(amount > 0);
mod = amount % STR_ALLOC_GRAN;
if (!mod) {
newsize = amount;
}
else {
newsize = amount + STR_ALLOC_GRAN - mod;
}
alloced = newsize;
#ifdef USE_STRING_DATA_ALLOCATOR
newbuffer = stringDataAllocator.Alloc(alloced);
#else
newbuffer = new char[alloced];
#endif
if (keepold && data) {
data[len] = '\0';
strcpy(newbuffer, data);
}
if (data && data != baseBuffer) {
#ifdef USE_STRING_DATA_ALLOCATOR
stringDataAllocator.Free(data);
#else
delete[] data;
#endif
}
data = newbuffer;
}
/*
============
idStr::FreeData
============
*/
void idStr::FreeData(void) {
if (data && data != baseBuffer) {
#ifdef USE_STRING_DATA_ALLOCATOR
stringDataAllocator.Free(data);
#else
delete[] data;
#endif
data = baseBuffer;
}
}
/*
============
idStr::operator=
============
*/
void idStr::operator=(const char* text) {
int l;
int diff;
int i;
if (!text) {
// safe behaviour if NULL
EnsureAlloced(1, false);
data[0] = '\0';
len = 0;
return;
}
if (text == data) {
return; // copying same thing
}
// check if we're aliasing
if (text >= data && text <= data + len) {
diff = text - data;
assert(strlen(text) < (unsigned)len);
for (i = 0; text[i]; i++) {
data[i] = text[i];
}
data[i] = '\0';
len -= diff;
return;
}
l = strlen(text);
EnsureAlloced(l + 1, false);
strcpy(data, text);
len = l;
}
/*
============
idStr::FindChar
returns -1 if not found otherwise the index of the char
============
*/
int idStr::FindChar(const char* str, const char c, int start, int end) {
int i;
if (end == -1) {
end = strlen(str) - 1;
}
for (i = start; i <= end; i++) {
if (str[i] == c) {
return i;
}
}
return -1;
}
/*
============
idStr::FindText
returns -1 if not found otherwise the index of the text
============
*/
int idStr::FindText(const char* str, const char* text, bool casesensitive, int start, int end) {
int l, i, j;
if (end == -1) {
end = strlen(str);
}
l = end - strlen(text);
for (i = start; i <= l; i++) {
if (casesensitive) {
for (j = 0; text[j]; j++) {
if (str[i + j] != text[j]) {
break;
}
}
}
else {
for (j = 0; text[j]; j++) {
if (::toupper(str[i + j]) != ::toupper(text[j])) {
break;
}
}
}
if (!text[j]) {
return i;
}
}
return -1;
}
/*
============
idStr::Filter
Returns true if the string conforms the given filter.
Several metacharacter may be used in the filter.
* match any string of zero or more characters
? match any single character
[abc...] match any of the enclosed characters; a hyphen can
be used to specify a range (e.g. a-z, A-Z, 0-9)
============
*/
bool idStr::Filter(const char* filter, const char* name, bool casesensitive) {
idStr buf;
int i, found, index;
while (*filter) {
if (*filter == '*') {
filter++;
buf.Empty();
for (i = 0; *filter; i++) {
if (*filter == '*' || *filter == '?' || (*filter == '[' && *(filter + 1) != '[')) {
break;
}
buf += *filter;
if (*filter == '[') {
filter++;
}
filter++;
}
if (buf.Length()) {
index = idStr(name).Find(buf.c_str(), casesensitive);
if (index == -1) {
return false;
}
name += index + strlen(buf);
}
}
else if (*filter == '?') {
filter++;
name++;
}
else if (*filter == '[') {
if (*(filter + 1) == '[') {
if (*name != '[') {
return false;
}
filter += 2;
name++;
}
else {
filter++;
found = false;
while (*filter && !found) {
if (*filter == ']' && *(filter + 1) != ']') {
break;
}
if (*(filter + 1) == '-' && *(filter + 2) && (*(filter + 2) != ']' || *(filter + 3) == ']')) {
if (casesensitive) {
if (*name >= *filter && *name <= *(filter + 2)) {
found = true;
}
}
else {
if (::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter + 2))) {
found = true;
}
}
filter += 3;
}
else {
if (casesensitive) {
if (*filter == *name) {
found = true;
}
}
else {
if (::toupper(*filter) == ::toupper(*name)) {
found = true;
}
}
filter++;
}
}
if (!found) {
return false;
}
while (*filter) {
if (*filter == ']' && *(filter + 1) != ']') {
break;
}
filter++;
}
filter++;
name++;
}
}
else {
if (casesensitive) {
if (*filter != *name) {
return false;
}
}
else {
if (::toupper(*filter) != ::toupper(*name)) {
return false;
}
}
filter++;
name++;
}
}
return true;
}
/*
=============
idStr::StripMediaName
makes the string lower case, replaces backslashes with forward slashes, and removes extension
=============
*/
void idStr::StripMediaName(const char* name, idStr& mediaName) {
char c;
mediaName.Empty();
for (c = *name; c; c = *(++name)) {
// truncate at an extension
if (c == '.') {
break;
}
// convert backslashes to forward slashes
if (c == '\\') {
mediaName.Append('/');
}
else {
mediaName.Append(idStr::ToLower(c));
}
}
}
/*
=============
idStr::CheckExtension
=============
*/
bool idStr::CheckExtension(const char* name, const char* ext) {
const char* s1 = name + Length(name) - 1;
const char* s2 = ext + Length(ext) - 1;
int c1, c2, d;
do {
c1 = *s1--;
c2 = *s2--;
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
return false;
}
} while (s1 > name && s2 > ext);
return (s1 >= name);
}
/*
=============
idStr::FloatArrayToString
=============
*/
const char* idStr::FloatArrayToString(const float* array, const int length, const int precision) {
static int index = 0;
static char str[4][16384]; // in case called by nested functions
int i, n;
char format[16], * s;
// use an array of string so that multiple calls won't collide
s = str[index];
index = (index + 1) & 3;
idStr::snPrintf(format, sizeof(format), "%%.%df", precision);
n = idStr::snPrintf(s, sizeof(str[0]), format, array[0]);
if (precision > 0) {
while (n > 0 && s[n - 1] == '0') s[--n] = '\0';
while (n > 0 && s[n - 1] == '.') s[--n] = '\0';
}
idStr::snPrintf(format, sizeof(format), " %%.%df", precision);
for (i = 1; i < length; i++) {
n += idStr::snPrintf(s + n, sizeof(str[0]) - n, format, array[i]);
if (precision > 0) {
while (n > 0 && s[n - 1] == '0') s[--n] = '\0';
while (n > 0 && s[n - 1] == '.') s[--n] = '\0';
}
}
return s;
}
/*
============
idStr::Last
returns -1 if not found otherwise the index of the char
============
*/
int idStr::Last(const char c) const {
int i;
for (i = Length(); i > 0; i--) {
if (data[i - 1] == c) {
return i - 1;
}
}
return -1;
}
/*
============
idStr::StripLeading
============
*/
void idStr::StripLeading(const char c) {
while (data[0] == c) {
memmove(&data[0], &data[1], len);
len--;
}
}
/*
============
idStr::StripLeading
============
*/
void idStr::StripLeading(const char* string) {
int l;
l = strlen(string);
if (l > 0) {
while (!Cmpn(string, l)) {
memmove(data, data + l, len - l + 1);
len -= l;
}
}
}
/*
============
idStr::StripLeadingOnce
============
*/
bool idStr::StripLeadingOnce(const char* string) {
int l;
l = strlen(string);
if ((l > 0) && !Cmpn(string, l)) {
memmove(data, data + l, len - l + 1);
len -= l;
return true;
}
return false;
}
/*
============
idStr::StripTrailing
============
*/
void idStr::StripTrailing(const char c) {
int i;
for (i = Length(); i > 0 && data[i - 1] == c; i--) {
data[i - 1] = '\0';
len--;
}
}
/*
============
idStr::StripLeading
============
*/
void idStr::StripTrailing(const char* string) {
int l;
l = strlen(string);
if (l > 0) {
while ((len >= l) && !Cmpn(string, data + len - l, l)) {
len -= l;
data[len] = '\0';
}
}
}
/*
============
idStr::StripTrailingOnce
============
*/
bool idStr::StripTrailingOnce(const char* string) {
int l;
l = strlen(string);
if ((l > 0) && (len >= l) && !Cmpn(string, data + len - l, l)) {
len -= l;
data[len] = '\0';
return true;
}
return false;
}
/*
============
idStr::Replace
============
*/
#ifdef QUAKE4
int idStr::Replace(const char* old, const char* nw) {
int iReplaced = 0;
int oldLen, newLen, i, j, count;
idStr oldString(data);
oldLen = strlen(old);
newLen = strlen(nw);
// Work out how big the new string will be
count = 0;
for (i = 0; i < oldString.Length(); i++) {
if (!idStr::Cmpn(&oldString[i], old, oldLen)) {
count++;
i += oldLen - 1;
}
}
if (count) {
EnsureAlloced(len + ((newLen - oldLen) * count) + 2, false);
// Replace the old data with the new data
for (i = 0, j = 0; i < oldString.Length(); i++) {
if (!idStr::Cmpn(&oldString[i], old, oldLen)) {
memcpy(data + j, nw, newLen);
i += oldLen - 1;
j += newLen;
iReplaced++;
}
else {
data[j] = oldString[i];
j++;
}
}
data[j] = 0;
len = strlen(data);
}
return iReplaced;
}
#else
void idStr::Replace(const char* old, const char* nw) {
int oldLen, newLen, i, j, count;
idStr oldString(data);
oldLen = strlen(old);
newLen = strlen(nw);
// Work out how big the new string will be
count = 0;
for (i = 0; i < oldString.Length(); i++) {
if (!idStr::Cmpn(&oldString[i], old, oldLen)) {
count++;
i += oldLen - 1;
}
}
if (count) {
EnsureAlloced(len + ((newLen - oldLen) * count) + 2, false);
// Replace the old data with the new data
for (i = 0, j = 0; i < oldString.Length(); i++) {
if (!idStr::Cmpn(&oldString[i], old, oldLen)) {
memcpy(data + j, nw, newLen);
i += oldLen - 1;
j += newLen;
}
else {
data[j] = oldString[i];
j++;
}
}
data[j] = 0;
len = strlen(data);
}
}
#endif
/*
============
idStr::Mid
============
*/
const char* idStr::Mid(int start, int len, idStr& result) const {
int i;
result.Empty();
i = Length();
if (i == 0 || len <= 0 || start >= i) {
return NULL;
}
if (start + len >= i) {
len = i - start;
}
result.Append(&data[start], len);
return result;
}
/*
============
idStr::Mid
============
*/
idStr idStr::Mid(int start, int len) const {
int i;
idStr result;
i = Length();
if (i == 0 || len <= 0 || start >= i) {
return result;
}
if (start + len >= i) {
len = i - start;
}
result.Append(&data[start], len);
return result;
}
/*
============
idStr::StripTrailingWhitespace
============
*/
void idStr::StripTrailingWhitespace(void) {
int i;
// cast to unsigned char to prevent stripping off high-ASCII characters
for (i = Length(); i > 0 && (unsigned char)(data[i - 1]) <= ' '; i--) {
data[i - 1] = '\0';
len--;
}
}
#ifdef QUAKE4
/*
============
idStr::StripUntil
============
*/
void idStr::StripUntil(const char c) {
while (data[0] != c && len) {
memmove(&data[0], &data[1], len);
len--;
}
}
#endif
/*
============
idStr::StripQuotes
Removes the quotes from the beginning and end of the string
============
*/
idStr& idStr::StripQuotes(void)
{
if (data[0] != '\"')
{
return *this;
}
// Remove the trailing quote first
if (data[len - 1] == '\"')
{
data[len - 1] = '\0';
len--;
}
// Strip the leading quote now
len--;
memmove(&data[0], &data[1], len);
data[len] = '\0';
return *this;
}
/*
=====================================================================
filename methods
=====================================================================
*/
/*
============
idStr::FileNameHash
============
*/
int idStr::FileNameHash(void) const {
int i;
long hash;
char letter;
hash = 0;
i = 0;
while (data[i] != '\0') {
letter = idStr::ToLower(data[i]);
if (letter == '.') {
break; // don't include extension
}
if (letter == '\\') {
letter = '/';
}
hash += (long)(letter) * (i + 119);
i++;
}
hash &= (FILE_HASH_SIZE - 1);
return hash;
}
/*
============
idStr::BackSlashesToSlashes
============
*/
idStr& idStr::BackSlashesToSlashes(void) {
int i;
for (i = 0; i < len; i++) {
if (data[i] == '\\') {
data[i] = '/';
}
}
return *this;
}
#ifdef QUAKE4
/*
============
idStr::SlashesToBackSlashes
============
*/
idStr& idStr::SlashesToBackSlashes(void) {
for (int i = 0; i < len; i++) {
if (data[i] == '/') {
data[i] = '\\';
}
}
return *this;
}
bool idStr::HasChar(const char check) {
for (int i = 0; i < len; i++) {
if (data[i] == check) {
return true;
}
}
return false;
}
bool idStr::HasChars(const char* check) {
while (*check) {
for (int i = 0; i < len; i++) {
if (data[i] == *check) {
return true;
}
}
check++;
}
return false;
}
idStr& idStr::ReplaceChar(const char from, const char to) {
for (int i = 0; i < len; i++) {
if (data[i] == from) {
data[i] = to;
}
}
return *this;
}
idStr& idStr::ReplaceChars(const char* from, const char to) {
while (*from) {
for (int i = 0; i < len; i++) {
if (data[i] == *from) {
data[i] = to;
}
}
from++;
}
return *this;
}
#endif
/*
============
idStr::SetFileExtension
============
*/
idStr& idStr::SetFileExtension(const char* extension) {
StripFileExtension();
if (*extension != '.') {
Append('.');
}
Append(extension);
return *this;
}
/*
============
idStr::StripFileExtension
============
*/
idStr& idStr::StripFileExtension(void) {
int i;
for (i = len - 1; i >= 0; i--) {
if (data[i] == '.') {
data[i] = '\0';
len = i;
break;
}
}
return *this;
}
/*
============
idStr::StripAbsoluteFileExtension
============
*/
idStr& idStr::StripAbsoluteFileExtension(void) {
int i;
for (i = 0; i < len; i++) {
if (data[i] == '.') {
data[i] = '\0';
len = i;
break;
}
}
return *this;
}
/*
==================
idStr::DefaultFileExtension
==================
*/
idStr& idStr::DefaultFileExtension(const char* extension) {
int i;
// do nothing if the string already has an extension
for (i = len - 1; i >= 0; i--) {
if (data[i] == '.') {
return *this;
}
}
if (*extension != '.') {
Append('.');
}
Append(extension);
return *this;
}
/*
==================
idStr::DefaultPath
==================
*/
idStr& idStr::DefaultPath(const char* basepath) {
if (((*this)[0] == '/') || ((*this)[0] == '\\')) {
// absolute path location
return *this;
}
*this = basepath + *this;
return *this;
}
/*
====================
idStr::AppendPath
====================
*/
void idStr::AppendPath(const char* text) {
int pos;
int i = 0;
if (text && text[i]) {
pos = len;
EnsureAlloced(len + strlen(text) + 2);
if (pos) {
if (data[pos - 1] != '/') {
data[pos++] = '/';
}
}
if (text[i] == '/') {
i++;
}
for (; text[i]; i++) {
if (text[i] == '\\') {
data[pos++] = '/';
}
else {
data[pos++] = text[i];
}
}
len = pos;
data[pos] = '\0';
}
}
/*
==================
idStr::StripFilename
==================
*/
idStr& idStr::StripFilename(void) {
int pos;
pos = Length() - 1;
while ((pos > 0) && ((*this)[pos] != '/') && ((*this)[pos] != '\\')) {
pos--;
}
if (pos < 0) {
pos = 0;
}
CapLength(pos);
return *this;
}
/*
==================
idStr::StripPath
==================
*/
idStr& idStr::StripPath(void) {
int pos;
pos = Length();
while ((pos > 0) && ((*this)[pos - 1] != '/') && ((*this)[pos - 1] != '\\')) {
pos--;
}
*this = Right(Length() - pos);
return *this;
}
/*
====================
idStr::ExtractFilePath
====================
*/
void idStr::ExtractFilePath(idStr& dest) const {
int pos;
//
// back up until a \ or the start
//
pos = Length();
while ((pos > 0) && ((*this)[pos - 1] != '/') && ((*this)[pos - 1] != '\\')) {
pos--;
}
Left(pos, dest);
}
/*
====================
idStr::ExtractFileName
====================
*/
void idStr::ExtractFileName(idStr& dest) const {
int pos;
//
// back up until a \ or the start
//
pos = Length() - 1;
while ((pos > 0) && ((*this)[pos - 1] != '/') && ((*this)[pos - 1] != '\\')) {
pos--;
}
Right(Length() - pos, dest);
}
/*
====================
idStr::ExtractFileBase
====================
*/
void idStr::ExtractFileBase(idStr& dest) const {
int pos;
int start;
//
// back up until a \ or the start
//
pos = Length() - 1;
while ((pos > 0) && ((*this)[pos - 1] != '/') && ((*this)[pos - 1] != '\\')) {
pos--;
}
start = pos;
while ((pos < Length()) && ((*this)[pos] != '.')
#ifdef QUAKE4
&& ((*this)[pos] != ',')
#endif
) {
pos++;
}
Mid(start, pos - start, dest);
}
/*
====================
idStr::ExtractFileExtension
====================
*/
void idStr::ExtractFileExtension(idStr& dest) const {
int pos;
//
// back up until a . or the start
//
pos = Length() - 1;
while ((pos > 0) && ((*this)[pos - 1] != '.')) {
pos--;
}
if (!pos) {
// no extension
dest.Empty();
}
else {
Right(Length() - pos, dest);
}
}
#ifdef QUAKE4
void idStr::ScrubFileName(void) {
RemoveEscapes();
StripFileExtension();
for (int i = 0; i < len; i++) {
byte c = (byte)data[i];
if (!idStr::CharIsAlpha(c) && !idStr::CharIsNumeric(c)) {
data[i] = '_';
}
}
}
void idStr::MakeNameCanonical(void) {
ToLower();
BackSlashesToSlashes();
StripFileExtension();
}
void idStr::EnsurePrintable(void) {
for (int i = 0; i < len; i++) {
if (!CharIsPrintable((byte)data[i])) {
memmove(&data[i], &data[i + 1], len - i);
len--;
i--;
}
}
}
#endif
/*
=====================================================================
char * methods to replace library functions
=====================================================================
*/
/*
============
idStr::IsNumeric
Checks a string to see if it contains only numerical values.
============
*/
bool idStr::IsNumeric(const char* s) {
int i;
bool dot;
if (*s == '-') {
s++;
}
dot = false;
for (i = 0; s[i]; i++) {
#ifdef QUAKE4
if (!isdigit((byte)s[i])) {
#else
if (!isdigit(s[i])) {
#endif
if ((s[i] == '.') && !dot) {
dot = true;
continue;
}
return false;
}
}
return true;
}
/*
============
idStr::HasLower
Checks if a string has any lowercase chars
============
*/
bool idStr::HasLower(const char* s) {
if (!s) {
return false;
}
while (*s) {
if (CharIsLower(*s)) {
return true;
}
s++;
}
return false;
}
/*
============
idStr::HasUpper
Checks if a string has any uppercase chars
============
*/
bool idStr::HasUpper(const char* s) {
if (!s) {
return false;
}
while (*s) {
if (CharIsUpper(*s)) {
return true;
}
s++;
}
return false;
}
/*
================
idStr::Cmp
================
*/
int idStr::Cmp(const char* s1, const char* s2) {
int c1, c2, d;
do {
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
if (d) {
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0; // strings are equal
}
/*
================
idStr::Cmpn
================
*/
int idStr::Cmpn(const char* s1, const char* s2, int n) {
int c1, c2, d;
assert(n >= 0);
do {
c1 = *s1++;
c2 = *s2++;
if (!n--) {
return 0; // strings are equal until end point
}
d = c1 - c2;
if (d) {
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0; // strings are equal
}
/*
================
idStr::Icmp
================
*/
int idStr::Icmp(const char* s1, const char* s2) {
int c1, c2, d;
do {
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0; // strings are equal
}
/*
================
idStr::Icmpn
================
*/
int idStr::Icmpn(const char* s1, const char* s2, int n) {
int c1, c2, d;
assert(n >= 0);
do {
c1 = *s1++;
c2 = *s2++;
if (!n--) {
return 0; // strings are equal until end point
}
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0; // strings are equal
}
/*
================
idStr::Icmp
================
*/
int idStr::IcmpNoColor(const char* s1, const char* s2) {
int c1, c2, d;
do {
while (idStr::IsColor(s1)) {
s1 += 2;
}
while (idStr::IsColor(s2)) {
s2 += 2;
}
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0; // strings are equal
}
#ifdef QUAKE4
/*
================
idStr::IcmpNoEscape
================
*/
int idStr::IcmpNoEscape(const char* s1, const char* s2) {
int c1, c2, d;
do {
for (d = idStr::IsEscape(s1); d; d = idStr::IsEscape(s1)) {
s1 += d;
}
for (d = idStr::IsEscape(s2); d; d = idStr::IsEscape(s2)) {
s2 += d;
}
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0;
}
#endif
/*
================
idStr::IcmpPath
================
*/
int idStr::IcmpPath(const char* s1, const char* s2) {
int c1, c2, d;
#if 0
//#if !defined( _WIN32 )
idLib::common->Printf("WARNING: IcmpPath used on a case-sensitive filesystem?\n");
#endif
do {
c1 = *s1++;
c2 = *s2++;
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c1 == '\\') {
d += ('/' - '\\');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
if (c2 == '\\') {
d -= ('/' - '\\');
if (!d) {
break;
}
}
// make sure folders come first
while (c1) {
if (c1 == '/' || c1 == '\\') {
break;
}
c1 = *s1++;
}
while (c2) {
if (c2 == '/' || c2 == '\\') {
break;
}
c2 = *s2++;
}
if (c1 && !c2) {
return -1;
}
else if (!c1 && c2) {
return 1;
}
// same folder depth so use the regular compare
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0;
}
/*
================
idStr::IcmpnPath
================
*/
int idStr::IcmpnPath(const char* s1, const char* s2, int n) {
int c1, c2, d;
#if 0
//#if !defined( _WIN32 )
idLib::common->Printf("WARNING: IcmpPath used on a case-sensitive filesystem?\n");
#endif
assert(n >= 0);
do {
c1 = *s1++;
c2 = *s2++;
if (!n--) {
return 0; // strings are equal until end point
}
d = c1 - c2;
while (d) {
if (c1 <= 'Z' && c1 >= 'A') {
d += ('a' - 'A');
if (!d) {
break;
}
}
if (c1 == '\\') {
d += ('/' - '\\');
if (!d) {
break;
}
}
if (c2 <= 'Z' && c2 >= 'A') {
d -= ('a' - 'A');
if (!d) {
break;
}
}
if (c2 == '\\') {
d -= ('/' - '\\');
if (!d) {
break;
}
}
// make sure folders come first
while (c1) {
if (c1 == '/' || c1 == '\\') {
break;
}
c1 = *s1++;
}
while (c2) {
if (c2 == '/' || c2 == '\\') {
break;
}
c2 = *s2++;
}
if (c1 && !c2) {
return -1;
}
else if (!c1 && c2) {
return 1;
}
// same folder depth so use the regular compare
return (INTSIGNBITNOTSET(d) << 1) - 1;
}
} while (c1);
return 0;
}
/*
=============
idStr::Copynz
Safe strncpy that ensures a trailing zero
=============
*/
void idStr::Copynz(char* dest, const char* src, int destsize) {
if (!src) {
idLib::common->Warning("idStr::Copynz: NULL src");
return;
}
if (destsize < 1) {
idLib::common->Warning("idStr::Copynz: destsize < 1");
return;
}
strncpy(dest, src, destsize - 1);
dest[destsize - 1] = 0;
}
/*
================
idStr::Append
never goes past bounds or leaves without a terminating 0
================
*/
void idStr::Append(char* dest, int size, const char* src) {
int l1;
l1 = strlen(dest);
if (l1 >= size) {
idLib::common->Error("idStr::Append: already overflowed");
}
idStr::Copynz(dest + l1, src, size - l1);
}
/*
================
idStr::LengthWithoutColors
================
*/
int idStr::LengthWithoutColors(const char* s) {
int len;
const char* p;
if (!s) {
return 0;
}
len = 0;
p = s;
while (*p) {
if (idStr::IsColor(p)) {
p += 2;
continue;
}
p++;
len++;
}
return len;
}
/*
================
idStr::RemoveColors
================
*/
char* idStr::RemoveColors(char* string) {
char* d;
char* s;
int c;
s = string;
d = string;
while ((c = *s) != 0) {
if (idStr::IsColor(s)) {
s++;
}
else {
*d++ = c;
}
s++;
}
*d = '\0';
return string;
}
#ifdef QUAKE4
/*
================
idStr::LengthWithoutEscapes
================
*/
int idStr::LengthWithoutEscapes(const char* s) {
int len;
const char* p;
if (!s) {
return 0;
}
len = 0;
p = s;
while (*p) {
int esc = idStr::IsEscape(p);
if (esc) {
p += esc;
continue;
}
p++;
len++;
}
return len;
}
/*
================
idStr::RemoveEscapes
================
*/
char* idStr::RemoveEscapes(char* string, int escapes) {
char* d;
char* s;
int c;
s = string;
d = string;
while ((c = *s) != 0) {
int type;
int esc = idStr::IsEscape(s, &type);
if (esc && (type & escapes)) {
s += esc;
continue;
}
else {
*d++ = c;
if (c == C_COLOR_ESCAPE && *(s + 1)) {
s++;
}
}
s++;
}
*d = '\0';
return string;
}
/*
================
idStr::IsEscape
================
*/
int idStr::IsEscape(const char* s, int* type) {
if (!s || *s != C_COLOR_ESCAPE || *(s + 1) == C_COLOR_ESCAPE) {
return 0;
}
if (type) {
*type = S_ESCAPE_UNKNOWN;
}
switch (*(s + 1)) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case ':':
if (type) {
*type = S_ESCAPE_COLORINDEX;
}
return 2;
case '-': case '+':
if (type) {
*type = S_ESCAPE_COLOR;
}
return 2;
case 'r': case 'R':
if (type) {
*type = S_ESCAPE_COMMAND;
}
return 2;
case 'c': case 'C':
if (*(s + 2) && *(s + 3) && *(s + 4)) {
if (type) {
*type = S_ESCAPE_COLOR;
}
return 5;
}
return 0;
case 'n': case 'N':
if (type) {
*type = S_ESCAPE_COMMAND;
}
if (*(s + 2)) {
return 3;
}
return 0;
case 'i': case 'I':
if (*(s + 2) && *(s + 3) && *(s + 4)) {
if (type) {
*type = S_ESCAPE_ICON;
}
return 5;
}
return 0;
}
return 0;
}
#endif
/*
================
idStr::snPrintf
================
*/
int idStr::snPrintf(char* dest, int size, const char* fmt, ...) {
int len;
va_list argptr;
char buffer[32000]; // big, but small enough to fit in PPC stack
va_start(argptr, fmt);
len = vsprintf(buffer, fmt, argptr);
va_end(argptr);
if (len >= sizeof(buffer)) {
idLib::common->Error("idStr::snPrintf: overflowed buffer");
}
if (len >= size) {
idLib::common->Warning("idStr::snPrintf: overflow of %i in %i\n", len, size);
len = size;
}
idStr::Copynz(dest, buffer, size);
return len;
}
/*
============
idStr::vsnPrintf
vsnprintf portability:
C99 standard: vsnprintf returns the number of characters (excluding the trailing
'\0') which would have been written to the final string if enough space had been available
snprintf and vsnprintf do not write more than size bytes (including the trailing '\0')
win32: _vsnprintf returns the number of characters written, not including the terminating null character,
or a negative value if an output error occurs. If the number of characters to write exceeds count, then count
characters are written and -1 is returned and no trailing '\0' is added.
idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0)
or returns -1 on failure or if the buffer would be overflowed.
============
*/
int idStr::vsnPrintf(char* dest, int size, const char* fmt, va_list argptr) {
int ret;
#ifdef _WIN32
#undef _vsnprintf
ret = _vsnprintf(dest, size - 1, fmt, argptr);
#define _vsnprintf use_idStr_vsnPrintf
#else
#undef vsnprintf
ret = vsnprintf(dest, size, fmt, argptr);
#define vsnprintf use_idStr_vsnPrintf
#endif
dest[size - 1] = '\0';
if (ret < 0 || ret >= size) {
return -1;
}
return ret;
}
/*
============
sprintf
Sets the value of the string using a printf interface.
============
*/
int sprintf(idStr & string, const char* fmt, ...) {
int l;
va_list argptr;
char buffer[32000];
va_start(argptr, fmt);
l = idStr::vsnPrintf(buffer, sizeof(buffer) - 1, fmt, argptr);
va_end(argptr);
buffer[sizeof(buffer) - 1] = '\0';
string = buffer;
return l;
}
/*
============
vsprintf
Sets the value of the string using a vprintf interface.
============
*/
int vsprintf(idStr & string, const char* fmt, va_list argptr) {
int l;
char buffer[32000];
l = idStr::vsnPrintf(buffer, sizeof(buffer) - 1, fmt, argptr);
buffer[sizeof(buffer) - 1] = '\0';
string = buffer;
return l;
}
/*
============
va
does a varargs printf into a temp buffer
NOTE: not thread safe
============
*/
char* va(const char* fmt, ...) {
va_list argptr;
static int index = 0;
#ifdef QUAKE4
static char string[VA_NUM_BUFS][VA_BUF_LEN]; // in case called by nested functions
#else
static char string[4][16384]; // in case called by nested functions
#endif
char* buf;
buf = string[index];
#ifdef QUAKE4
index = (index + 1) & 7;
#else
index = (index + 1) & 3;
#endif
va_start(argptr, fmt);
vsprintf(buf, fmt, argptr);
va_end(argptr);
return buf;
}
#ifdef QUAKE4
#ifdef _WIN32
/*
============
fe
Used for formatting windows errors
NOTE: not thread safe
============
*/
char* fe(int errorId) {
static int index = 0;
static char string[4][256];
char* buf;
buf = string[index];
index = (index + 1) & 3;
#ifndef _XBOX
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errorId,
0,
(LPTSTR)buf,
256,
NULL);
#endif
return buf;
}
#endif
#endif
/*
============
idStr::BestUnit
============
*/
int idStr::BestUnit(const char* format, float value, Measure_t measure) {
int unit = 1;
while (unit <= 3 && (1 << (unit * 10) < value)) {
unit++;
}
unit--;
value /= 1 << (unit * 10);
sprintf(*this, format, value);
*this += " ";
*this += units[measure][unit];
return unit;
}
/*
============
idStr::SetUnit
============
*/
void idStr::SetUnit(const char* format, float value, int unit, Measure_t measure) {
value /= 1 << (unit * 10);
sprintf(*this, format, value);
*this += " ";
*this += units[measure][unit];
}
/*
================
idStr::InitMemory
================
*/
void idStr::InitMemory(void) {
#ifdef USE_STRING_DATA_ALLOCATOR
#ifdef QUAKE4
#ifdef MEM_SCOPED_TAG
MEM_SCOPED_TAG(tag, MA_STRING);
#endif
#endif
stringDataAllocator.Init();
#endif
}
/*
================
idStr::ShutdownMemory
================
*/
void idStr::ShutdownMemory(void) {
#ifdef USE_STRING_DATA_ALLOCATOR
stringDataAllocator.Shutdown();
#endif
}
/*
================
idStr::PurgeMemory
================
*/
void idStr::PurgeMemory(void) {
#ifdef USE_STRING_DATA_ALLOCATOR
stringDataAllocator.FreeEmptyBaseBlocks();
#endif
}
/*
================
idStr::ShowMemoryUsage_f
================
*/
void idStr::ShowMemoryUsage_f(const idCmdArgs & args) {
#ifdef USE_STRING_DATA_ALLOCATOR
idLib::common->Printf("%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n",
stringDataAllocator.GetBaseBlockMemory() >> 10, stringDataAllocator.GetFreeBlockMemory() >> 10,
stringDataAllocator.GetNumFreeBlocks(), stringDataAllocator.GetNumEmptyBaseBlocks());
#endif
}
/*
================
idStr::FormatNumber
================
*/
struct formatList_t {
int gran;
int count;
};
// elements of list need to decend in size
formatList_t formatList[] = {
{ 1000000000, 0 },
{ 1000000, 0 },
{ 1000, 0 }
};
int numFormatList = sizeof(formatList) / sizeof(formatList[0]);
idStr idStr::FormatNumber(int number) {
idStr string;
bool hit;
// reset
for (int i = 0; i < numFormatList; i++) {
formatList_t* li = formatList + i;
li->count = 0;
}
// main loop
do {
hit = false;
for (int i = 0; i < numFormatList; i++) {
formatList_t* li = formatList + i;
if (number >= li->gran) {
li->count++;
number -= li->gran;
hit = true;
break;
}
}
} while (hit);
// print out
bool found = false;
for (int i = 0; i < numFormatList; i++) {
formatList_t* li = formatList + i;
if (li->count) {
if (!found) {
string += va("%i,", li->count);
}
else {
string += va("%3.3i,", li->count);
}
found = true;
}
else if (found) {
string += va("%3.3i,", li->count);
}
}
if (found) {
string += va("%3.3i", number);
}
else {
string += va("%i", number);
}
// pad to proper size
int count = 11 - string.Length();
for (int i = 0; i < count; i++) {
string.Insert(" ", 0);
}
return string;
}
#ifdef QUAKE4
/*
================
idStr::Split
================
*/
void idStr::Split(const char* source, idList<idStr> &list, const char delimiter, const char groupDelimiter) {
const idStr localSource(source);
int sourceLength = localSource.Length();
idStr element;
int startIndex = 0;
int endIndex = -1;
char currentChar = '\0';
list.Clear();
while (startIndex < sourceLength) {
currentChar = localSource[startIndex];
if (currentChar == groupDelimiter) {
endIndex = localSource.Find(groupDelimiter, ++startIndex);
if (endIndex == -1) {
common->Error("Couldn't find expected char %c in idStr::Split\n", groupDelimiter);
}
element = localSource.Mid(startIndex, endIndex);
element.Strip(groupDelimiter);
list.Append(element);
element.Clear();
startIndex = endIndex + 1;
continue;
}
else if (currentChar == delimiter) {
element += '\0';
list.Append(element);
element.Clear();
endIndex = ++startIndex;
continue;
}
startIndex++;
element += currentChar;
}
if (element.Length()) {
element += '\0';
list.Append(element);
}
}
/*
================
idStr::Split
================
*/
void idStr::Split(idList<idStr> &list, const char delimiter, const char groupDelimiter) {
Split(c_str(), list, delimiter, groupDelimiter);
}
idStr idStr::GetLastColorCode(void) const {
for (int i = Length(); i > 0; i--) {
int escapeType = 0;
int escapeLength = idStr::IsEscape(&data[i - 1], &escapeType);
if (escapeLength && (escapeType == S_ESCAPE_COLORINDEX || escapeType == S_ESCAPE_COLOR)) {
idStr result = "";
result.Append(&data[i - 1], escapeLength);
return result;
}
}
return "";
}
#endif
#ifdef QUAKE4
// P means the character is Polish
// C means the character is Czech
const bool idStr::printableCharacter[256] =
{
//
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
//
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
// ! " # $ & % ' ( ) * + , - . /
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// @ A B C D E F G H I J K L M N O
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// P Q R S T U V W X Y Z [ \ ] ^ _
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// ` a b c d e f g h i j k l m n o
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// p q r s t u v w x y z { | } ~
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false,
// € X S C Œ P T C Z C Z P
true, true, false, false, false, false, false, false, false, false, true, false, true, true, true, true,
// ™ s C œ P t C z C z P
false, false, false, false, false, false, false, false, false, true, true, false, true, true, true, true,
// ¡ £ P ¤ ¥ P § © « ® P
false, true, false, true, true, true, false, true, false, true, false, true, false, false, true, true,
// ° £ P ´ µ · P » ¿ P
true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, true,
// À Á C Â Ã Ä Å Æ P Ç È C É C Ê P Ë Ì C Í C Î Ï C
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// Ð Ñ P Ò C Ó PC Ô Õ Ö × Ø C Ù C Ú C Û Ü Ý C Þ ß
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// à á C â ã ä å æ P ç è C é C ê P ë ì C í C î ï C
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
// ð ñ P ò C ó PC ô õ ö ÷ ø C ù C ú C û ü ý C þ ÿ
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
};
const char idStr::upperCaseCharacter[256] =
{
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// ! " # $ & % ' ( ) * + , - . /
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// @ A B C D E F G H I J K L M N O
0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
// P Q R S T U V W X Y Z [ \ ] ^ _
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0,
// ` a b c d e f g h i j k l m n o
0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
// p q r s t u v w x y z { | } ~
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0,
// € X S Œ T Z
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Š', 0, 'Œ', '', 'Ž', 'Ž',
// TM s œ t z
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Š', 0, 'Œ', '', 'Ž', '',
// ¡ £ ¤ ¥ § © « ®
0, 0, 0, '£', 0, '¥', 0, 0, 0, 0, 0, 0, 0, 0, 0, '¿',
// ° ´ µ · » ¿
0, 0, 0, '£', 0, 0, 0, 0, 0, '¥', 0, 0, 0, 0, 0, '¿',
// À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï
'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï',
// Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß
'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0, 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß',
// à á â ã ä å æ ç è é ê ë ì í î ï
'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï',
// ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ
'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0, 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß',
};
const char idStr::lowerCaseCharacter[256] =
{
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
//
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// ! " # $ & % ' ( ) * + , - . /
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// @ A B C D E F G H I J K L M N O
0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
// P Q R S T U V W X Y Z [ \ ] ^ _
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0,
// ` a b c d e f g h i j k l m n o
0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
// p q r s t u v w x y z { | } ~
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0,
// € X S Œ T Z
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'š', 0, 'œ', '', 'ž', 'Ÿ',
// TM s œ t z
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'š', 0, 'œ', '', 'ž', 'Ÿ',
// ¡ £ ¤ ¥ § © « ®
0, 0, 0, '³', 0, '¹', 0, 0, 0, 0, 0, 0, 0, 0, 0, '¯',
// ° ´ µ · » ¿
0, 0, 0, '³', 0, 0, 0, 0, 0, '¹', 0, 0, 0, 0, 0, '¯',
// À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï
'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï',
// Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß
'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0, 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß',
// à á â ã ä å æ ç è é ê ë ì í î ï
'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï',
// ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ
'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0, 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß',
};
// RAVEN END
#endif