Files

938 lines
25 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA 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 2 of the License,
or (at your option) any later version.
OpenMoHAA 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// configurator.cpp: Config class for INI files.
#include "configurator.h"
CLASS_DECLARATION(Class, Configurator, NULL) {
{NULL, NULL}
};
Configurator::Configurator(const char *filename)
{
Parse(filename);
}
Configurator::Configurator()
{
m_bNoWrite = false;
m_bNeedWrite = false;
}
Configurator::~Configurator()
{
Close();
}
size_t Configurator::GetLine(char *dest, const char *data, size_t size)
{
const char *p = data;
size_t i = 0;
while (*p == '\n') {
p++;
}
if (p >= data + size) {
return 0;
}
while (*p && *p != '\n' && i < size) {
dest[i++] = *p++;
}
if (i < size) {
dest[i++] = '\n';
}
dest[i] = 0;
return p - data;
}
void Configurator::RemoveSection(configSection_t *section)
{
configKey_t *key;
int i;
for (i = 0; i < section->key.NumObjects(); i++) {
key = section->key[i];
RemoveKey(section, key);
}
m_reverseSections.RemoveObject(section);
m_sections.remove(section->name);
}
void Configurator::RemoveKey(configSection_t *section, configKey_t *key)
{
section->key.RemoveObject(key);
delete key;
}
configSection_t *Configurator::CreateSection(const char *section)
{
configSection_t *s = &m_sections.addKeyValue(section);
if (s->key.NumObjects()) {
RemoveSection(s);
s = &m_sections.addKeyValue(section);
} else {
m_reverseSections.AddObject(s);
}
s->name = section;
return s;
}
int Configurator::GetKeyArray(char *key)
{
int count;
size_t len;
int arrayindex = -1;
char *p1, *p2;
len = strlen(key) + 1;
p1 = new char[len];
p2 = new char[len];
count = sscanf(key, "%[^[] [%[^]]", p1, p2);
if (count == 2) {
// Check for an array [num]
strcpy(key, p1);
arrayindex = atoi(p2);
if (arrayindex < 0 || arrayindex > MAX_CONFIGURATOR_ARRAY_SIZE) {
arrayindex = -2;
}
}
delete[] p1;
delete[] p2;
return arrayindex;
}
int Configurator::GetKeyArray(str& key)
{
int count;
size_t len;
int arrayindex = -1;
str arrayval;
char *p1, *p2;
len = key.length() + 1;
p1 = new char[len];
p2 = new char[len];
count = sscanf(key, "%[^[] [%[^]]", p1, p2);
if (count == 2) {
// Check for an array [num]
arrayval = p2;
arrayindex = atoi(arrayval.c_str());
key = p1;
if (arrayindex < 0 || arrayindex > MAX_CONFIGURATOR_ARRAY_SIZE) {
arrayindex = -2;
}
}
delete[] p1;
delete[] p2;
return arrayindex;
}
configKey_t *Configurator::CreateKey(configSection_t *section, const char *key, unsigned int *index)
{
configKey_t *k;
str keyname;
int arrayindex;
if (index) {
*index = 0;
}
keyname = key;
arrayindex = GetKeyArray(keyname);
if (arrayindex == -2) {
return NULL;
} else if (arrayindex >= 0) {
if (index) {
*index = arrayindex;
}
}
k = FindKey(section, keyname);
if (k) {
if (arrayindex == -1 || !k->value.NumObjects()) {
RemoveKey(section, k);
k = NULL;
}
}
if (!k) {
k = new configKey_t;
section->key.AddObject(k);
k->name = keyname;
k->m_bArray = arrayindex != -1;
k->m_bNeedWrite = false;
}
if (arrayindex >= k->value.MaxObjects()) {
k->value.Resize((arrayindex + 1) * 2);
}
return k;
}
configSection_t *Configurator::FindSection(const char *section)
{
configSection_t *s = m_sections.findKeyValue(section);
return s;
}
configKey_t *Configurator::FindKey(configSection_t *section, const char *key)
{
configKey_t *k;
int i;
for (i = 0; i < section->key.NumObjects(); i++) {
k = section->key[i];
if (!k->name.icmp(key)) {
return k;
}
}
return NULL;
}
int Configurator::CutLine(char *data)
{
char *p = data;
while (*p == '\n') {
*p++ = 0;
}
while (*p && *p != '\n') {
*p++ = 0;
}
return p - data;
}
bool Configurator::SetupLine(char *line, int& lineno, size_t& len, size_t& last)
{
lineno++;
len = (int)strlen(line) - 1;
if (len <= 0) {
return false;
}
if (line[len] != '\n') {
last = 0;
glbs.DPrintf("Configurator::SetupLine: input line too long in %s (line %d)\n", m_filename.c_str(), lineno);
return false;
}
while ((len >= 0) && ((line[len] == '\n') || (isspace(line[len])))) {
line[len] = 0;
len--;
}
if (line[len] == '\\') {
last = len;
return false;
} else {
last = 0;
}
return true;
}
void Configurator::WriteData(char **data, size_t *size)
{
configSection_t *section;
configKey_t *key;
configValue_t *val;
str value;
char *oldData = *data;
char *currentData = *data;
size_t currentSize = *size;
size_t len, tlen = 0;
int i, j, k;
size_t offset, offset2;
bool bFound;
for (i = 1; i <= m_reverseSections.NumObjects(); i++) {
section = m_reverseSections.ObjectAt(i);
if (!FindData(LINE_SECTION, section->name, NULL, &offset, currentData, currentSize)) {
oldData = currentData;
len = (offset ? 2 : 0) + 1 + section->name.length() + 1; // \n\n + [ + section + ]
currentSize += len;
currentData = new char[currentSize + 1];
strncpy(currentData, oldData, offset);
Com_sprintf(
currentData + offset, currentSize + 1 - offset, offset ? "\n\n[%s]" : "[%s]", section->name.c_str()
);
Q_strncpyz(currentData + offset + len, oldData + offset, currentSize + 1 - offset - len);
if (currentData != oldData) {
delete[] oldData;
}
}
for (j = 0; j < section->key.NumObjects(); j++) {
key = section->key[j];
if (!key->m_bNeedWrite) {
continue;
}
if (key->m_bArray) {
for (k = 0; k < key->value.NumObjects(); k++) {
val = &key->value[k];
if (!val->m_bNeedWrite) {
continue;
}
value = val->value;
bFound = FindData(
LINE_VALUE, section->name, key->name + "[" + k + "]", &offset, currentData, currentSize
);
{
int digits, l;
oldData = currentData;
l = k;
digits = 0;
if (!l) {
l = 1;
}
while (l) {
l /= 10;
digits++;
}
if (bFound) {
len = CutLine(currentData + offset);
currentSize -= len;
offset2 = offset + len;
} else {
offset2 = offset;
}
len = 1 + key->name.length() + 1 + digits + 1 + 1
+ value.length(); // \n + name + [ + digits + ] + = + value
currentSize += len;
currentData = new char[currentSize + 1];
strncpy(currentData, oldData, offset);
Com_sprintf(
currentData + offset,
currentSize + 1 - offset,
"\n%s[%d]=%s",
key->name.c_str(),
k,
value.c_str()
);
Q_strncpyz(currentData + offset + len, oldData + offset2, currentSize + 1 - offset - len);
if (currentData != oldData) {
delete[] oldData;
}
}
}
} else {
val = &key->value[0];
if (!val->m_bNeedWrite) {
continue;
}
value = val->value;
bFound = FindData(LINE_VALUE, section->name, key->name, &offset, currentData, currentSize);
{
oldData = currentData;
if (bFound) {
len = CutLine(currentData + offset);
currentSize -= len;
offset2 = offset + len;
} else {
offset2 = offset;
}
len = 1 + key->name.length() + 1 + value.length(); // \n + name + = + value
currentSize += len;
currentData = new char[currentSize + 1];
strncpy(currentData, oldData, offset);
Com_sprintf(
currentData + offset, currentSize + 1 - offset "\n%s=%s", key->name.c_str(), value.c_str()
);
Q_strncpyz(currentData + offset + len, oldData + offset2, currentSize + 1 - offset - len);
if (currentData != oldData) {
delete[] oldData;
}
}
}
}
}
if (currentData[currentSize - 1] != '\n') {
currentData[currentSize] = '\n';
currentSize++;
}
*data = currentData;
*size = currentSize;
}
void Configurator::WriteData2(char **data, size_t *size)
{
configSection_t *section;
configKey_t *key;
configValue_t *val;
str value;
char *oldData = *data;
char *currentData = *data;
size_t currentSize = *size;
char *p = currentData;
size_t tlen = 0;
int i, j, k;
for (i = 1; i <= m_reverseSections.NumObjects(); i++) {
section = m_reverseSections.ObjectAt(i);
if (i == 1) {
tlen += 1 + section->name.length() + 1 + 1; // [ + name + ] + \n
} else {
tlen += 1 + 1 + section->name.length() + 1 + 1; // \n + [ name + ] + \n
}
if (tlen > currentSize) {
oldData = currentData;
currentSize = tlen + 1024;
currentData = new char[currentSize];
p = currentData + (p - oldData);
Q_strncpyz(currentData, oldData, currentSize);
delete[] oldData;
}
if (i == 1) {
p += Com_sprintf(p, currentSize - (p - currentData), "[%s]\n", section->name.c_str());
} else {
p += Com_sprintf(p, currentSize - (p - currentData), "\n[%s]\n", section->name.c_str());
}
for (j = 0; j < section->key.NumObjects(); j++) {
key = section->key[j];
if (!key->m_bNeedWrite) {
continue;
}
if (key->m_bArray) {
for (k = 0; k < key->value.NumObjects(); k++) {
val = &key->value[k];
if (!val->m_bNeedWrite) {
continue;
}
value = val->value;
{
int digits, l;
l = k;
digits = 0;
if (!l) {
l = 1;
}
while (l) {
l /= 10;
digits++;
}
tlen += key->name.length() + 1 + digits + 1 + 1 + value.length()
+ 1; // name + [ + digits + ] + = + value + \n
if (tlen > currentSize) {
oldData = currentData;
currentSize = tlen + 1024;
currentData = new char[currentSize];
p = currentData + (p - oldData);
strcpy(currentData, oldData);
delete[] oldData;
}
p += Com_sprintf(
p, currentSize - (p - currentData), "%s[%d]=%s\n", key->name.c_str(), k, value.c_str()
);
}
}
} else {
val = &key->value[0];
if (!val->m_bNeedWrite) {
continue;
}
value = val->value;
tlen += key->name.length() + 1 + value.length() + 1; // name + = + value + \n
if (tlen > currentSize) {
oldData = currentData;
currentSize = tlen + 1024;
currentData = new char[currentSize];
p = currentData + (p - oldData);
strcpy(currentData, oldData);
delete[] oldData;
}
p += Com_sprintf(p, currentSize - (p - currentData), "%s=%s\n", key->name.c_str(), value.c_str());
}
}
}
*data = currentData;
*size = tlen;
}
bool Configurator::FindData(int type, const char *s, const char *k, size_t *offset, const char *data, size_t size)
{
static char line[MAX_STRING_TOKENS + 1];
static char section[MAX_STRING_TOKENS + 1];
static char key[MAX_STRING_TOKENS + 1];
static char val[MAX_STRING_TOKENS + 1];
size_t last = 0;
size_t len;
size_t olen = 0;
size_t tlen = 0;
int lineno = 0;
int arrayindex;
bool bFoundSection = false;
*offset = 0;
while ((olen = GetLine(line + last, data + tlen, MAX_STRING_TOKENS - last)) != 0) {
tlen += olen;
if (!SetupLine(line + last, lineno, len, last)) {
continue;
}
switch (ParseLine(line, section, key, val)) {
case LINE_SECTION:
if (bFoundSection) {
// Return the last line of the section to append
*offset = tlen - olen;
return false;
}
if (!Q_stricmp(s, section)) {
bFoundSection = true;
if (type == LINE_SECTION) {
*offset = tlen;
return true;
}
}
break;
case LINE_VALUE:
arrayindex = GetKeyArray(key);
if (arrayindex >= 0) {
Com_sprintf(key, sizeof(key), "%s[%d]", key, arrayindex);
}
if (type == LINE_VALUE && bFoundSection && !Q_stricmp(k, key)) {
*offset = tlen - olen;
return true;
}
break;
default:
break;
}
}
*offset = tlen;
return false;
}
void Configurator::ParseData(const char *data, size_t size)
{
static char line[MAX_STRING_TOKENS + 1];
static char section[MAX_STRING_TOKENS + 1];
static char key[MAX_STRING_TOKENS + 1];
static char val[MAX_STRING_TOKENS + 1];
size_t last = 0;
size_t len;
size_t tlen = 0;
int lineno = 0;
unsigned int index = 0;
configSection_t *current;
configKey_t *k;
configValue_t v;
while ((len = GetLine(line + last, data + tlen, MAX_STRING_TOKENS - last)) != 0) {
tlen += len;
if (!SetupLine(line + last, lineno, len, last)) {
continue;
}
switch (ParseLine(line, section, key, val)) {
case LINE_EMPTY:
case LINE_COMMENT:
break;
case LINE_SECTION:
current = CreateSection(section);
break;
case LINE_VALUE:
k = CreateKey(current, key, &index);
if (!k) {
glbs.DPrintf("Configurator::ParseData: Could not create key:\n");
goto __error;
}
v.m_bNeedWrite = false;
v.value = val;
k->value.AddObjectAt(index + 1, v);
break;
case LINE_ERROR:
glbs.DPrintf("Configurator::ParseData: syntax error:\n");
__error:
glbs.DPrintf("(%s, %d):", m_filename.c_str(), lineno);
glbs.DPrintf("%s\n", line);
break;
default:
break;
}
}
}
int Configurator::ParseLine(char *line, char *section, char *key, char *value)
{
size_t len;
int result;
len = strlen(line);
if (len < 1) {
result = LINE_EMPTY;
} else if (line[0] == ';') {
result = LINE_COMMENT;
} else if (line[0] == '[' && line[len - 1] == ']') {
sscanf(line, "[%[^]]", section);
strcpy(section, strstrip(section));
strlwc(section);
result = LINE_SECTION;
} else if ((sscanf(line, "%[^=] = \"%[^\"]\"", key, value)) == 2
|| (sscanf(line, "%[^=] = %[^;]", key, value)) == 2) {
strcpy(key, strstrip(key));
strlwc(key);
strcpy(value, strstrip(value));
// sscanf cannot handle empty "" values
if (!strcmp(value, "\"\"")) {
value = "";
}
result = LINE_VALUE;
} else if ((sscanf(line, "%[^=] = %[;]", key, value)) == 2 || (sscanf(line, "%[^=] %[=]", key, value)) == 2) {
strcpy(key, strstrip(key));
strlwc(key);
value[0] = 0;
result = LINE_VALUE;
} else {
result = LINE_ERROR;
}
return result;
}
str Configurator::GetValue(const char *section, const char *key, str defaultValue, int index)
{
configKey_t *k = GetKey(section, key, index);
configValue_t val;
if (!k) {
return defaultValue;
}
if (index < 0) {
index = 0;
}
if (k->m_bNeedWrite) {
val.m_bNeedWrite = true;
val.value = defaultValue;
k->value.AddObjectAt(index + 1, val);
}
return k->value[index].value;
}
configKey_t *Configurator::GetKey(const char *section, const char *key, int index)
{
configSection_t *s;
configKey_t *k;
bool bArray = index >= 0;
if (index < 0) {
index = 0;
}
s = FindSection(section);
if (!s) {
if (m_bNoWrite) {
return NULL;
}
s = CreateSection(section);
s->m_bNeedWrite = true;
m_bNeedWrite = true;
}
k = FindKey(s, key);
if (!k) {
if (m_bNoWrite) {
return NULL;
}
str keyname;
if (bArray) {
keyname = str(key) + "[" + str(index) + "]";
} else {
keyname = key;
}
k = CreateKey(s, keyname, NULL);
k->m_bNeedWrite = true;
m_bNeedWrite = true;
} else if (index >= k->value.NumObjects()) {
k->m_bNeedWrite = true;
m_bNeedWrite = true;
}
return k;
}
//======================================
// Configurator::GetString
//======================================
str Configurator::GetString(const char *section, const char *key, str defaultValue, int index)
{
str value = GetValue(section, key, defaultValue, index);
return value;
}
//======================================
// Configurator::GetInteger
//======================================
int Configurator::GetInteger(const char *section, const char *key, int defaultValue, int index)
{
str value = GetValue(section, key, defaultValue, index);
return atoi(value.c_str());
}
//======================================
// Configurator::GetFloat
//======================================
float Configurator::GetFloat(const char *section, const char *key, float defaultValue, int index)
{
str value = GetValue(section, key, defaultValue, index);
return atof(value.c_str());
}
//======================================
// Configurator::SetString
//======================================
void Configurator::SetString(const char *section, const char *key, str value, int index)
{
configKey_t *k = GetKey(section, key, index);
configValue_t v;
if (!k) {
return;
}
if (index < 0) {
index = 0;
}
if (index >= k->value.NumObjects() || k->value[index].value != value) {
m_bNeedWrite = true;
v.m_bNeedWrite = true;
v.value = value;
k->m_bNeedWrite = true;
k->value.AddObjectAt(index + 1, v);
}
}
//======================================
// Configurator::SetInteger
//======================================
void Configurator::SetInteger(const char *section, const char *key, int value, int index)
{
return SetString(section, key, value, index);
}
//======================================
// Configurator::SetFloat
//======================================
void Configurator::SetFloat(const char *section, const char *key, float value, int index)
{
return SetString(section, key, value, index);
}
//======================================
// Configurator::SetWrite
//======================================
void Configurator::SetWrite(bool bWrite)
{
m_bNoWrite = !bWrite;
}
//======================================
// Configurator::Parse
//======================================
void Configurator::Parse(const char *filename)
{
FILE *file;
size_t size;
char *buffer;
m_bNoWrite = false;
m_bNeedWrite = false;
this->m_filename = filename;
file = fopen(filename, "r");
if (!file) {
return;
}
fseek(file, 0, SEEK_END);
size = ftell(file);
rewind(file);
buffer = new char[size + 1];
size = fread(buffer, 1, size, file);
buffer[size] = 0;
fclose(file);
ParseData(buffer, size);
}
//======================================
// Configurator::Close
//======================================
void Configurator::Close(void)
{
FILE *file;
char *buffer;
int i;
size_t size;
if (!m_filename.length() || !m_bNeedWrite) {
goto _cleanup;
}
file = fopen(m_filename.c_str(), "r+");
if (!file) {
file = fopen(m_filename.c_str(), "w+");
if (!file) {
return;
}
file = freopen(m_filename.c_str(), "r+", file);
if (!file) {
return;
}
}
fseek(file, 0, SEEK_END);
size = ftell(file);
rewind(file);
buffer = new char[size + 1];
size = fread(buffer, 1, size, file);
buffer[size] = 0;
glbs.DPrintf("Configurator::Close: Writing data to %s...\n", m_filename.c_str());
WriteData(&buffer, &size);
glbs.DPrintf("Configurator::Close: Done.\n");
file = freopen(m_filename.c_str(), "w+", file);
fwrite(buffer, size, 1, file);
fclose(file);
m_filename = "";
delete[] buffer;
_cleanup:
for (i = m_reverseSections.NumObjects(); i > 0; i--) {
RemoveSection(m_reverseSections.ObjectAt(i));
}
}
void test_config(void)
{
Configurator config("test.ini");
str value;
value = config.GetString("section1", "testvar1", "100");
glbs.DPrintf("value = %s\n", value.c_str());
value = config.GetString("section1", "testarray1", "100", 0);
glbs.DPrintf("array1 = %s\n", value.c_str());
value = config.GetString("section1", "testarray1", "100", 1);
glbs.DPrintf("array2 = %s\n", value.c_str());
value = config.GetString("section1", "testarray1", "100", 2);
glbs.DPrintf("array3 = %s\n", value.c_str());
value = config.GetString("section1", "testforcewrite", "500");
glbs.DPrintf("testforcewrite = %s\n", value.c_str());
value = config.GetString("section2", "testforcewrite2", "500");
glbs.DPrintf("testforcewrite2 = %s\n", value.c_str());
//for( int i = 0; i < 2048; i++ )
//{
// value = config.GetString( "section1", "testforcewrite5", "2048", i );
//}
config.SetString("section1", "testforcewrite5", "newstring", 2048);
config.Close();
}