diff --git a/source/ps/CStrIntern.cpp b/source/ps/CStrIntern.cpp
new file mode 100644
index 0000000000..3c18b32757
--- /dev/null
+++ b/source/ps/CStrIntern.cpp
@@ -0,0 +1,137 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "CStrIntern.h"
+
+#include "lib/fnv_hash.h"
+#include "ps/CLogger.h"
+
+#include
+
+class CStrInternInternals
+{
+public:
+ CStrInternInternals(const char* str, size_t len)
+ : data(str, str+len), hash(fnv_hash(str, len))
+ {
+// LOGWARNING(L"New interned string '%hs'", data.c_str());
+ }
+
+ bool operator==(const CStrInternInternals& b) const
+ {
+ // Compare hash first for quick rejection of inequal strings
+ return (hash == b.hash && data == b.data);
+ }
+
+ const std::string data;
+ const u32 hash; // fnv_hash of data
+
+private:
+ CStrInternInternals& operator=(const CStrInternInternals&);
+};
+
+// Interned strings are stored in a hash table, indexed by string:
+
+typedef std::string StringsKey;
+
+struct StringsKeyHash
+{
+ size_t operator()(const StringsKey& key) const
+ {
+ return fnv_hash(key.c_str(), key.length());
+ }
+};
+
+// To avoid std::string memory allocations when GetString does lookups in the
+// hash table of interned strings, we make use of boost::unordered_map's ability
+// to do lookups with a functionally equivalent proxy object:
+
+struct StringsKeyProxy
+{
+ const char* str;
+ size_t len;
+};
+
+struct StringsKeyProxyHash
+{
+ size_t operator()(const StringsKeyProxy& key) const
+ {
+ return fnv_hash(key.str, key.len);
+ }
+};
+
+struct StringsKeyProxyEq
+{
+ bool operator()(const StringsKeyProxy& proxy, const StringsKey& key) const
+ {
+ return (proxy.len == key.length() && memcmp(proxy.str, key.c_str(), proxy.len) == 0);
+ }
+};
+
+static boost::unordered_map, StringsKeyHash> g_Strings;
+
+
+static CStrInternInternals* GetString(const char* str, size_t len)
+{
+ // g_Strings is not thread-safe, so complain if anyone is using this
+ // type in non-main threads. (If that's desired, g_Strings should be changed
+ // to be thread-safe, preferably without sacrificing performance.)
+ ENSURE(ThreadUtil::IsMainThread());
+
+ StringsKeyProxy proxy = { str, len };
+ boost::unordered_map >::iterator it =
+ g_Strings.find(proxy, StringsKeyProxyHash(), StringsKeyProxyEq());
+
+ if (it != g_Strings.end())
+ return it->second.get();
+
+ shared_ptr internals(new CStrInternInternals(str, len));
+ g_Strings.insert(std::make_pair(internals->data, internals));
+ return internals.get();
+}
+
+CStrIntern::CStrIntern()
+{
+ m = GetString("", 0);
+}
+
+CStrIntern::CStrIntern(const char* str)
+{
+ m = GetString(str, strlen(str));
+}
+
+CStrIntern::CStrIntern(const std::string& str)
+{
+ m = GetString(str.c_str(), str.length());
+}
+
+u32 CStrIntern::GetHash() const
+{
+ return m->hash;
+}
+
+const char* CStrIntern::c_str() const
+{
+ return m->data.c_str();
+}
+
+const std::string& CStrIntern::string() const
+{
+ return m->data;
+}
diff --git a/source/ps/CStrIntern.h b/source/ps/CStrIntern.h
new file mode 100644
index 0000000000..10f63405e4
--- /dev/null
+++ b/source/ps/CStrIntern.h
@@ -0,0 +1,81 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_CSTRINTERN
+#define INCLUDED_CSTRINTERN
+
+class CStrInternInternals;
+
+/**
+ * Interned 8-bit strings.
+ * Each instance with the same string content is a pointer to the same piece of
+ * memory, allowing very fast string comparisons.
+ *
+ * Since a CStrIntern is just a dumb pointer, copying is very fast,
+ * and pass-by-value should be preferred over pass-by-reference.
+ *
+ * Memory allocated for strings will never be freed, so don't use this for
+ * unbounded numbers of strings (e.g. text rendered by gameplay scripts) -
+ * it's intended for a small number of short frequently-used strings.
+ *
+ * Not thread-safe - only allocate these strings from the main thread.
+ */
+class CStrIntern
+{
+public:
+ CStrIntern();
+ explicit CStrIntern(const char* str);
+ explicit CStrIntern(const std::string& str);
+
+ /**
+ * Returns cached FNV1-A hash of the string.
+ */
+ u32 GetHash() const;
+
+ /**
+ * Returns null-terminated string.
+ */
+ const char* c_str() const;
+
+ /**
+ * Returns as std::string.
+ */
+ const std::string& string() const;
+
+ /**
+ * String equality.
+ */
+ bool operator==(const CStrIntern& b) const
+ {
+ return m == b.m;
+ }
+
+ /**
+ * Compare with some arbitrary total order.
+ * (In particular, this is not alphabetic order,
+ * and is not consistent between runs of the game.)
+ */
+ bool operator<(const CStrIntern& b) const
+ {
+ return m < b.m;
+ }
+
+private:
+ CStrInternInternals* m;
+};
+
+#endif // INCLUDED_CSTRINTERN
\ No newline at end of file