You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
767 lines
19 KiB
767 lines
19 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/***************************************************************************** |
|
* |
|
* $/Plasma20/Sources/Plasma/NucleusLib/pnIni/Private/pnIniCore.cpp |
|
* |
|
***/ |
|
|
|
#include "../Pch.h" |
|
#pragma hdrstop |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Internal functions |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
static wchar * TrimWhitespace (wchar * name) { |
|
while (isspace((char) *name)) |
|
++name; |
|
|
|
for (wchar * term = name; *term; ++term) { |
|
if (isspace((char) *term)) { |
|
*term = 0; |
|
break; |
|
} |
|
} |
|
|
|
return name; |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* IniValue |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
IniValue::IniValue (IniKey * key, unsigned lineNum) |
|
: fKey(key) |
|
, fLineNum(lineNum) |
|
{ |
|
fIndex = key->fValues.Add(this); |
|
} |
|
|
|
//=========================================================================== |
|
IniValue::~IniValue () { |
|
wchar ** cur = fArgs.Ptr(); |
|
wchar ** end = fArgs.Term(); |
|
for (; cur < end; ++cur) |
|
FREE(*cur); |
|
} |
|
|
|
//=========================================================================== |
|
static void AddValueString ( |
|
IniValue * value, |
|
const wchar src[] |
|
) { |
|
unsigned chars = StrLen(src) + 1; |
|
wchar * dst = ALLOCA(wchar, chars); |
|
StrTokenize(&src, dst, chars, L" \t\r\n\""); |
|
value->fArgs.Add(StrDup(dst)); |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* IniKey |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
IniKey::IniKey (IniSection * section, const wchar name[]) |
|
: fSection(section) |
|
{ |
|
StrCopy(fName, name, (unsigned) -1); |
|
fSection->fKeys.Add(this); |
|
} |
|
|
|
//=========================================================================== |
|
IniKey::~IniKey () { |
|
IniValue ** cur = fValues.Ptr(); |
|
IniValue ** end = fValues.Term(); |
|
for (; cur < end; ++cur) |
|
DEL(*cur); |
|
} |
|
|
|
//=========================================================================== |
|
inline unsigned IniKey::GetHash () const { |
|
return StrHashI(fName); |
|
} |
|
|
|
//=========================================================================== |
|
inline bool IniKey::operator== (const CHashKeyStrPtrI & rhs) const { |
|
return !StrCmpI(fName, rhs.GetString()); |
|
} |
|
|
|
//=========================================================================== |
|
static IniValue * AddKeyValue ( |
|
IniSection * section, |
|
wchar * string, |
|
unsigned lineNum |
|
) { |
|
string = TrimWhitespace(string); |
|
|
|
// Find or create the key |
|
IniKey * key = section->fKeys.Find(string); |
|
if (!key) { |
|
key = new(ALLOC( |
|
sizeof(*key) - sizeof(key->fName) + StrBytes(string) |
|
)) IniKey(section, string); |
|
} |
|
|
|
// Add a new value holder for the key |
|
return NEW(IniValue)(key, lineNum); |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* IniSection |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
IniSection::IniSection (const wchar name[]) { |
|
StrCopy(fName, name, (unsigned) -1); |
|
} |
|
|
|
//=========================================================================== |
|
IniSection::~IniSection () { |
|
fKeys.Clear(); |
|
} |
|
|
|
//=========================================================================== |
|
inline unsigned IniSection::GetHash () const { |
|
return StrHashI(fName); |
|
} |
|
|
|
//=========================================================================== |
|
inline bool IniSection::operator== (const CHashKeyStrPtrI & rhs) const { |
|
return !StrCmpI(fName, rhs.GetString()); |
|
} |
|
|
|
//=========================================================================== |
|
static IniSection * AddSection ( |
|
Ini * ini, |
|
wchar * string |
|
) { |
|
// Find or create the section |
|
IniSection * section = ini->fSections.Find(string); |
|
if (!section) { |
|
section = new(ALLOC( |
|
sizeof(*section) - sizeof(section->fName) + StrBytes(string) |
|
)) IniSection(string); |
|
ini->fSections.Add(section); |
|
} |
|
return section; |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* Ini |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
Ini::~Ini () { |
|
fSections.Clear(); |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* ParseBuffer |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
static void ParseBuffer ( |
|
Ini * ini, |
|
const wchar buffer[] |
|
) { |
|
|
|
const wchar SECTION_OPEN_CHAR = '['; |
|
const wchar SECTION_CLOSE_CHAR = ']'; |
|
const wchar EQUIVALENCE_CHAR = '='; |
|
const wchar VALUE_SEPARATOR = ','; |
|
const wchar COMMENT_CHAR = ';'; |
|
const wchar QUOTE_CHAR = '\"'; |
|
const wchar NEWLINE = '\n'; |
|
|
|
enum { |
|
STATE_BEGIN, |
|
STATE_NEWLINE, |
|
STATE_SECTION, |
|
STATE_STRIP_TRAILING, |
|
STATE_KEY, |
|
STATE_VALUE, |
|
} state = STATE_BEGIN; |
|
|
|
IniSection * section = nil; |
|
IniValue * value = nil; |
|
const wchar * start = nil; |
|
bool valInQuotes = false; |
|
wchar dst[512]; |
|
dst[0] = 0; |
|
|
|
for (unsigned lineNum = 1;; ++buffer) { |
|
|
|
// Get next character |
|
unsigned chr = *buffer; |
|
if (!chr) |
|
break; |
|
if (chr == '\r') |
|
continue; |
|
if (chr == '\n') |
|
++lineNum; |
|
if (chr == '\t') |
|
chr = ' '; |
|
|
|
switch (state) { |
|
case STATE_BEGIN: |
|
ASSERT(chr == UNICODE_BOM); |
|
state = STATE_NEWLINE; |
|
break; |
|
|
|
case STATE_NEWLINE: |
|
if (chr == NEWLINE) |
|
break; |
|
if (chr == ' ') |
|
break; |
|
if (chr == SECTION_OPEN_CHAR) { |
|
start = buffer + 1; |
|
state = STATE_SECTION; |
|
} |
|
else if (chr == COMMENT_CHAR) { |
|
state = STATE_STRIP_TRAILING; |
|
} |
|
else { |
|
start = buffer; |
|
state = STATE_KEY; |
|
} |
|
break; |
|
|
|
case STATE_SECTION: |
|
if (chr == NEWLINE) { |
|
state = STATE_NEWLINE; |
|
break; |
|
} |
|
|
|
if (chr == SECTION_CLOSE_CHAR) { |
|
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst))); |
|
section = AddSection(ini, dst); |
|
state = STATE_STRIP_TRAILING; |
|
} |
|
break; |
|
|
|
case STATE_STRIP_TRAILING: |
|
if (chr == NEWLINE) |
|
state = STATE_NEWLINE; |
|
break; |
|
|
|
case STATE_KEY: |
|
if (chr == NEWLINE) { |
|
state = STATE_NEWLINE; |
|
break; |
|
} |
|
if (chr != EQUIVALENCE_CHAR) |
|
break; |
|
|
|
if (!section) { |
|
state = STATE_STRIP_TRAILING; |
|
break; |
|
} |
|
|
|
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst))); |
|
value = AddKeyValue(section, dst, lineNum); |
|
start = buffer + 1; |
|
state = STATE_VALUE; |
|
valInQuotes = false; |
|
break; |
|
|
|
case STATE_VALUE: |
|
if (chr == QUOTE_CHAR) |
|
valInQuotes = !valInQuotes; |
|
if ((valInQuotes || chr != VALUE_SEPARATOR) && (chr != NEWLINE)) |
|
break; |
|
if (!value) { |
|
state = chr == NEWLINE ? STATE_NEWLINE : STATE_STRIP_TRAILING; |
|
break; |
|
} |
|
if (valInQuotes) { |
|
state = chr == NEWLINE ? STATE_NEWLINE : STATE_STRIP_TRAILING; |
|
break; |
|
} |
|
|
|
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst))); |
|
AddValueString(value, dst); |
|
if (chr == VALUE_SEPARATOR) |
|
start = buffer + 1; |
|
else |
|
state = STATE_NEWLINE; |
|
break; |
|
} |
|
} |
|
|
|
// cleanup current value |
|
if (state == STATE_VALUE) { |
|
StrCopy(dst, start, min(buffer - start + 1, arrsize(dst))); |
|
AddValueString(value, dst); |
|
} |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* ParseFile |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
static void IniFileNotifyProc ( |
|
AsyncFile , |
|
EAsyncNotifyFile , |
|
AsyncNotifyFile * , |
|
void ** |
|
) { |
|
} |
|
|
|
//=========================================================================== |
|
static bool ParseFile ( |
|
Ini * ini, |
|
const wchar fileName[] |
|
) { |
|
// Open file |
|
qword fileSize; |
|
qword fileLastWriteTime; |
|
EFileError error; |
|
AsyncFile file = AsyncFileOpen( |
|
fileName, |
|
IniFileNotifyProc, |
|
&error, |
|
kAsyncFileReadAccess, |
|
kAsyncFileModeOpenExisting, |
|
kAsyncFileShareRead, |
|
nil, |
|
&fileSize, |
|
&fileLastWriteTime |
|
); |
|
if (!file) |
|
return false; |
|
|
|
bool result; |
|
if (fileSize > 256 * 1024) { |
|
result = false; |
|
} |
|
else if (!fileSize) { |
|
result = true; |
|
} |
|
else { |
|
// Read entire file into memory and NULL terminate wchar |
|
byte * buffer = (byte *) ALLOC((unsigned) fileSize + sizeof(wchar)); |
|
AsyncFileRead(file, 0, buffer, (unsigned) fileSize, kAsyncFileRwSync, nil); |
|
* (wchar *) &buffer[fileSize] = 0; |
|
|
|
// Convert to unicode if necessary |
|
if (* (wchar *) buffer != UNICODE_BOM) { |
|
byte * src = buffer; |
|
// Allocate two extra spaces for UNICODE_BOM and terminator |
|
unsigned newBufferSize = ((unsigned) fileSize + 2) * sizeof(wchar); |
|
|
|
// Allocate new buffer |
|
wchar * dst = (wchar *) ALLOC(newBufferSize); |
|
|
|
// If it's UTF-8 file,convert to Unicode |
|
if (StrCmpI((char *)buffer, UTF8_BOM, StrLen(UTF8_BOM)) == 0) { |
|
// StrUtf8ToUnicode will convert UTF8_BOM to UNICODE_BOM |
|
StrUtf8ToUnicode(dst, (char *)src, newBufferSize); |
|
} |
|
else { |
|
// do simple conversion to Unicode |
|
dst[0] = UNICODE_BOM; |
|
for (unsigned index = 0;; ++index) { |
|
if (0 == (dst[index + 1] = src[index])) |
|
break; |
|
} |
|
} |
|
|
|
FREE(src); |
|
buffer = (byte *) dst; |
|
} |
|
|
|
ParseBuffer(ini, (const wchar *) buffer); |
|
FREE(buffer); |
|
result = true; |
|
} |
|
|
|
AsyncFileClose(file, kAsyncFileDontTruncate); |
|
return result; |
|
} |
|
|
|
|
|
/***************************************************************************** |
|
* |
|
* Exports |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
Ini * IniOpen ( |
|
const wchar fileName[] |
|
) { |
|
Ini * ini = NEW(Ini); |
|
if (!ParseFile(ini, fileName)) { |
|
IniClose(ini); |
|
return nil; |
|
} |
|
return ini; |
|
} |
|
|
|
//=========================================================================== |
|
void IniClose (Ini * ini) { |
|
DEL(ini); |
|
} |
|
|
|
//=========================================================================== |
|
const IniSection * IniGetFirstSection ( |
|
const Ini * ini, |
|
wchar * name, |
|
unsigned chars |
|
) { |
|
if (chars) |
|
*name = 0; |
|
if (!ini) |
|
return nil; |
|
|
|
const IniSection * section = ini->fSections.Head(); |
|
if (section) |
|
StrCopy(name, section->fName, chars); |
|
return section; |
|
} |
|
|
|
//=========================================================================== |
|
const IniSection * IniGetNextSection ( |
|
const IniSection * section, |
|
wchar * name, |
|
unsigned chars |
|
) { |
|
if (chars) |
|
*name = 0; |
|
if (!section) |
|
return nil; |
|
|
|
section = section->fLink.Next(); |
|
if (section) |
|
StrCopy(name, section->fName, chars); |
|
return section; |
|
} |
|
|
|
//=========================================================================== |
|
const IniSection * IniGetSection ( |
|
const Ini * ini, |
|
const wchar name[] |
|
) { |
|
if (!ini) |
|
return nil; |
|
|
|
return ini->fSections.Find( |
|
CHashKeyStrPtrI(name) |
|
); |
|
} |
|
|
|
//=========================================================================== |
|
const IniKey * IniGetFirstKey ( |
|
const IniSection * section, |
|
wchar * name, |
|
unsigned chars |
|
) { |
|
if (chars) |
|
*name = 0; |
|
if (!section) |
|
return nil; |
|
|
|
const IniKey * key = section->fKeys.Head(); |
|
if (key) |
|
StrCopy(name, key->fName, chars); |
|
return key; |
|
} |
|
|
|
//============================================================================ |
|
const IniKey * IniGetFirstKey ( |
|
const Ini * ini, |
|
const wchar sectionName[], |
|
wchar * name, |
|
unsigned chars |
|
) { |
|
if (const IniSection * section = IniGetSection(ini, sectionName)) |
|
return IniGetFirstKey(section, name, chars); |
|
|
|
return nil; |
|
} |
|
|
|
//=========================================================================== |
|
const IniKey * IniGetNextKey ( |
|
const IniKey * key, |
|
wchar * name, |
|
unsigned chars |
|
) { |
|
if (chars) |
|
*name = 0; |
|
if (!key) |
|
return nil; |
|
|
|
key = key->fLink.Next(); |
|
if (key) |
|
StrCopy(name, key->fName, chars); |
|
return key; |
|
} |
|
|
|
//=========================================================================== |
|
const IniKey * IniGetKey ( |
|
const IniSection * section, |
|
const wchar name[] |
|
) { |
|
if (!section) |
|
return nil; |
|
|
|
return section->fKeys.Find( |
|
CHashKeyStrPtrI(name) |
|
); |
|
} |
|
|
|
//=========================================================================== |
|
const IniValue * IniGetFirstValue ( |
|
const IniKey * key, |
|
unsigned * lineNum |
|
) { |
|
if (lineNum) |
|
*lineNum = 0; |
|
|
|
const IniValue * value = nil; |
|
for (;;) { |
|
if (!key) |
|
break; |
|
|
|
value = key->fValues[0]; |
|
if (lineNum) |
|
*lineNum = value->fLineNum; |
|
break; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
//=========================================================================== |
|
const IniValue * IniGetFirstValue ( |
|
const IniSection * section, |
|
const wchar keyName[], |
|
unsigned * lineNum |
|
) { |
|
const IniValue * value = nil; |
|
if (lineNum) |
|
*lineNum = 0; |
|
|
|
for (;;) { |
|
if (!section) |
|
break; |
|
|
|
const IniKey * key = section->fKeys.Find( |
|
CHashKeyStrPtrI(keyName) |
|
); |
|
value = IniGetFirstValue(key, lineNum); |
|
break; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
//=========================================================================== |
|
const IniValue * IniGetFirstValue ( |
|
const Ini * ini, |
|
const wchar sectionName[], |
|
const wchar keyName[], |
|
unsigned * lineNum |
|
) { |
|
const IniValue * value = nil; |
|
if (lineNum) |
|
*lineNum = 0; |
|
|
|
for (;;) { |
|
if (!ini) |
|
break; |
|
|
|
const IniSection * section = ini->fSections.Find( |
|
CHashKeyStrPtrI(sectionName) |
|
); |
|
|
|
value = IniGetFirstValue(section, keyName, lineNum); |
|
break; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
//=========================================================================== |
|
const IniValue * IniGetNextValue ( |
|
const IniValue * value, |
|
unsigned * lineNum |
|
) { |
|
if (lineNum) |
|
*lineNum = 0; |
|
|
|
const IniKey * key = value->fKey; |
|
if (value->fIndex + 1 < key->fValues.Count()) { |
|
value = key->fValues[value->fIndex + 1]; |
|
if (lineNum) |
|
*lineNum = value->fLineNum; |
|
} |
|
else { |
|
value = nil; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
//=========================================================================== |
|
bool IniGetUnsigned ( |
|
const IniValue * value, |
|
unsigned * result, |
|
unsigned index, |
|
unsigned defaultValue |
|
) { |
|
ASSERT(result); |
|
|
|
for (;;) { |
|
if (!value) |
|
break; |
|
|
|
wchar str[32]; |
|
if (!IniGetString(value, str, arrsize(str), index, nil)) |
|
break; |
|
|
|
if (!str[0]) |
|
break; |
|
|
|
*result = StrToUnsigned(str, nil, 0); |
|
return true; |
|
} |
|
|
|
*result = defaultValue; |
|
return false; |
|
} |
|
|
|
//=========================================================================== |
|
bool IniGetString ( |
|
const IniValue * value, |
|
wchar * result, |
|
unsigned resultChars, |
|
unsigned index, |
|
const wchar defaultValue[] |
|
) { |
|
ASSERT(result); |
|
|
|
bool found = value && index < value->fArgs.Count(); |
|
|
|
if (found) |
|
StrCopy(result, value->fArgs[index], resultChars); |
|
else if (defaultValue) |
|
StrCopy(result, defaultValue, resultChars); |
|
|
|
return found; |
|
} |
|
|
|
//=========================================================================== |
|
bool IniGetUuid ( |
|
const IniValue * value, |
|
Uuid * uuid, |
|
unsigned index, |
|
const Uuid & defaultValue |
|
) { |
|
wchar str[128]; |
|
if (IniGetString(value, str, arrsize(str), index, nil)) |
|
return GuidFromString(str, uuid); |
|
else |
|
*uuid = defaultValue; |
|
|
|
return false; |
|
} |
|
|
|
//=========================================================================== |
|
unsigned IniGetBoundedValue ( |
|
const IniValue * value, |
|
const wchar section[], |
|
const wchar key[], |
|
unsigned index, |
|
unsigned minVal, |
|
unsigned maxVal, |
|
unsigned defVal |
|
) { |
|
if (!value) |
|
return defVal; |
|
|
|
unsigned result; |
|
IniGetUnsigned(value, &result, index, defVal); |
|
if ((result < minVal) || (result > maxVal)) { |
|
result = defVal; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
//=========================================================================== |
|
unsigned IniGetBoundedValue ( |
|
const Ini * ini, |
|
const wchar section[], |
|
const wchar key[], |
|
unsigned index, |
|
unsigned minVal, |
|
unsigned maxVal, |
|
unsigned defVal |
|
) { |
|
unsigned lineNum; |
|
const IniValue * value = IniGetFirstValue( |
|
ini, |
|
section, |
|
key, |
|
&lineNum |
|
); |
|
|
|
return IniGetBoundedValue( |
|
value, |
|
section, |
|
key, |
|
index, |
|
minVal, |
|
maxVal, |
|
defVal |
|
); |
|
}
|
|
|