diff --git a/Sources/Plasma/CoreLib/CMakeLists.txt b/Sources/Plasma/CoreLib/CMakeLists.txt
index d2f65fb5..be0bf861 100644
--- a/Sources/Plasma/CoreLib/CMakeLists.txt
+++ b/Sources/Plasma/CoreLib/CMakeLists.txt
@@ -51,6 +51,7 @@ set(CoreLib_SOURCES
hsWide.cpp
pcSmallRect.cpp
plFileSystem.cpp
+ plFormat.cpp
plGeneric.cpp
plLoadMask.cpp
plProduct.cpp
@@ -96,6 +97,7 @@ set(CoreLib_HEADERS
hsWindows.h
pcSmallRect.h
plFileSystem.h
+ plFormat.h
plGeneric.h
plLoadMask.h
plProduct.h
diff --git a/Sources/Plasma/CoreLib/plFormat.cpp b/Sources/Plasma/CoreLib/plFormat.cpp
new file mode 100644
index 00000000..547fb657
--- /dev/null
+++ b/Sources/Plasma/CoreLib/plFormat.cpp
@@ -0,0 +1,187 @@
+/*==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 .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+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==*/
+
+#include "plFormat.h"
+
+#include "HeadSpin.h"
+
+namespace plFormat_Private
+{
+ static const char *_scanNextFormat(IFormatDataObject &data)
+ {
+ hsAssert(data.fFormatStr, "Passed a null format string!");
+
+ const char *ptr = data.fFormatStr;
+ while (*ptr) {
+ if (*ptr == '{')
+ return ptr;
+ ++ptr;
+ }
+
+ return ptr;
+ }
+
+ static void _fetchPrefixChunk(IFormatDataObject &data)
+ {
+ do {
+ const char *next = _scanNextFormat(data);
+ if (*next && *(next + 1) == '{') {
+ // Escaped '{'
+ data.fOutput.push_back(plStringBuffer(data.fFormatStr, 1 + next - data.fFormatStr));
+ data.fFormatStr = next + 2;
+ continue;
+ }
+
+ if (next != data.fFormatStr)
+ data.fOutput.push_back(plStringBuffer(data.fFormatStr, next - data.fFormatStr));
+ data.fFormatStr = next;
+ } while (0);
+ }
+
+ FormatSpec FetchNextFormat(IFormatDataObject &data)
+ {
+ _fetchPrefixChunk(data);
+ hsAssert(*data.fFormatStr == '{', "Too many actual parameters for format string");
+
+ FormatSpec spec;
+ const char *ptr = data.fFormatStr;
+ for ( ;; ) {
+ ++ptr;
+
+ switch (*ptr) {
+ case 0:
+ hsAssert(0, "Unterminated format specifier.");
+ abort();
+ case '}':
+ // Done with format spec
+ data.fFormatStr = ptr + 1;
+ return spec;
+ break;
+
+ case '<':
+ spec.fAlignment = kAlignLeft;
+ break;
+ case '>':
+ spec.fAlignment = kAlignRight;
+ break;
+ case '_':
+ spec.fPadChar = *(ptr + 1);
+ hsAssert(spec.fPadChar, "Unterminated format specifier");
+ ++ptr;
+ break;
+ case 'x':
+ spec.fDigitClass = kDigitHex;
+ break;
+ case 'X':
+ spec.fDigitClass = kDigitHexUpper;
+ break;
+ case 'd':
+ spec.fDigitClass = kDigitDec;
+ break;
+ case 'o':
+ spec.fDigitClass = kDigitOct;
+ break;
+ case 'b':
+ spec.fDigitClass = kDigitBin;
+ break;
+ case 'c':
+ spec.fDigitClass = kDigitChar;
+ break;
+ case 'f':
+ spec.fFloatClass = kFloatF;
+ break;
+ case 'g':
+ spec.fFloatClass = kFloatG;
+ break;
+ case 'e':
+ spec.fFloatClass = kFloatE;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ {
+ char *end = nullptr;
+ spec.fPrecisionLeft = strtol(ptr, &end, 10);
+ ptr = end - 1;
+ }
+ break;
+ case '.':
+ {
+ hsAssert(*(ptr + 1), "Unterminated format specifier");
+ char *end = nullptr;
+ spec.fPrecisionRight = strtol(ptr + 1, &end, 10);
+ ptr = end - 1;
+ }
+ break;
+ default:
+ hsAssert(0, "Unexpected character in format string");
+ break;
+ }
+ }
+ }
+
+ plString _IFormat(plFormat_Private::IFormatDataObject &data)
+ {
+ _fetchPrefixChunk(data);
+ hsAssert(*data.fFormatStr == 0, "Not enough actual parameters for format string");
+
+ size_t outsize = 0;
+ for (const plStringBuffer &buf : data.fOutput)
+ outsize += buf.GetSize();
+
+ plStringBuffer outbuf;
+ char *out_ptr = outbuf.CreateWritableBuffer(outsize);
+ for (const plStringBuffer &buf : data.fOutput) {
+ memcpy(out_ptr, buf.GetData(), buf.GetSize());
+ out_ptr += buf.GetSize();
+ }
+ *out_ptr = 0;
+
+ return outbuf;
+ }
+}
+
+PL_FORMAT_IMPL(int)
+{
+ char buffer[32];
+ int size = snprintf(buffer, 32, "%d", value);
+ return plStringBuffer(buffer, size);
+}
diff --git a/Sources/Plasma/CoreLib/plFormat.h b/Sources/Plasma/CoreLib/plFormat.h
new file mode 100644
index 00000000..be6768ad
--- /dev/null
+++ b/Sources/Plasma/CoreLib/plFormat.h
@@ -0,0 +1,176 @@
+/*==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 .
+
+Additional permissions under GNU GPL version 3 section 7
+
+If you modify this Program, or any covered work, by linking or
+combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
+NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
+JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
+(or a modified version of those libraries),
+containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
+PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
+JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
+licensors of this Program grant you additional
+permission to convey the resulting work. Corresponding Source for a
+non-source form of such a combination shall include the source code for
+the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
+work.
+
+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==*/
+
+#ifndef plFormat_Defined
+#define plFormat_Defined
+
+#include "plString.h"
+#include
+#include
+
+/* (TODO: Make this table doxygen-friendly)
+ *
+ * FORMAT SPECIFICATION
+ *
+ * {} - format a value (defaults)
+ * {{ - Escape for a single '{' char
+ * {options} - Format a value, with the specified options (see below)
+ *
+ * Options:
+ * < - Align left
+ * > - Align right
+ * NNN - Pad to NNN characters (minimum - can be more)
+ * _C - Use C as the pad character (only '\001'..'\177' supported for now)
+ * x - Hex (lower-case)
+ * X - Hex (upper-case)
+ * o - Octal
+ * b - Binary
+ * d - Decimal (default) -- when used with char types, outputs a
+ * number instead of the UTF representation of the char
+ * c - UTF character (default for character types)
+ * FFF.EEE - Use FFF.EEE floating point precision
+ * f - Use 'f' format for floating point numbers
+ * g - Use 'g' format for floating point numbers
+ * e - Use 'e' format for floating point numbers
+ */
+
+// For internal use by plFormat and its helper function
+namespace plFormat_Private
+{
+ enum Alignment : unsigned char
+ {
+ kAlignDefault, kAlignLeft, kAlignRight
+ };
+
+ enum DigitClass : unsigned char
+ {
+ kDigitDefault, kDigitDec, kDigitHex, kDigitHexUpper,
+ kDigitOct, kDigitBin, kDigitChar
+ };
+
+ enum FloatClass : unsigned char
+ {
+ kFloatDefault, kFloatE, kFloatF, kFloatG
+ };
+
+ struct FormatSpec
+ {
+ const char *fEnd;
+
+ int fPrecisionLeft = 0;
+ int fPrecisionRight = 0;
+
+ char fPadChar = 0;
+ Alignment fAlignment = kAlignDefault;
+ DigitClass fDigitClass = kDigitDefault;
+ FloatClass fFloatClass = kFloatDefault;
+ };
+
+ struct IFormatDataObject
+ {
+ const char *fFormatStr;
+ std::list> fOutput;
+ };
+
+ extern FormatSpec FetchNextFormat(IFormatDataObject &data);
+}
+
+// Fun fact: You can add your own formatters by declaring
+// PL_FORMAT_TYPE(mytype) in a header, and
+// PL_FORMAT_IMPL(mytype) { ... } in a source file
+
+#define PL_FORMAT_TYPE(_type) \
+ extern plStringBuffer _impl_plFormat_DataHandler( \
+ plFormat_Private::FormatSpec &format, _type value); \
+ namespace plFormat_Private \
+ { \
+ template \
+ plString _IFormat(IFormatDataObject &data, _type value, _Args... args) \
+ { \
+ plFormat_Private::FormatSpec format = plFormat_Private::FetchNextFormat(data); \
+ data.fOutput.push_back(_impl_plFormat_DataHandler(format, value)); \
+ return _IFormat(data, args...); \
+ } \
+ } \
+ template \
+ plString plFormat(const char *fmt_str, _type value, _Args... args) \
+ { \
+ plFormat_Private::IFormatDataObject data; \
+ data.fFormatStr = fmt_str; \
+ plFormat_Private::FormatSpec format = plFormat_Private::FetchNextFormat(data); \
+ data.fOutput.push_back(_impl_plFormat_DataHandler(format, value)); \
+ return plFormat_Private::_IFormat(data, args...); \
+ }
+
+#define PL_FORMAT_IMPL(_type) \
+ plStringBuffer _impl_plFormat_DataHandler( \
+ plFormat_Private::FormatSpec &format, _type value)
+
+PL_FORMAT_TYPE(char)
+PL_FORMAT_TYPE(wchar_t)
+PL_FORMAT_TYPE(signed char)
+PL_FORMAT_TYPE(unsigned char)
+PL_FORMAT_TYPE(short)
+PL_FORMAT_TYPE(unsigned short)
+PL_FORMAT_TYPE(int)
+PL_FORMAT_TYPE(unsigned)
+PL_FORMAT_TYPE(long)
+PL_FORMAT_TYPE(unsigned long)
+PL_FORMAT_TYPE(int64_t)
+PL_FORMAT_TYPE(uint64_t)
+PL_FORMAT_TYPE(float)
+PL_FORMAT_TYPE(double)
+PL_FORMAT_TYPE(const char *)
+PL_FORMAT_TYPE(const wchar_t *)
+PL_FORMAT_TYPE(const plString &)
+
+// TODO: Remove these when they're no longer needed
+PL_FORMAT_TYPE(const std::string &)
+PL_FORMAT_TYPE(const std::wstring &)
+
+// End of the chain -- emits the last piece (if any) and builds the final string
+namespace plFormat_Private
+{
+ plString _IFormat(IFormatDataObject &data);
+}
+
+#endif // plFormat_Defined