From c812c92d1a1835f0bda783e709481188c8d92225 Mon Sep 17 00:00:00 2001
From: Ben Longbons <b.r.longbons@gmail.com>
Date: Sat, 15 Mar 2014 19:34:59 -0700
Subject: Clean up header organization

---
 src/admin/GNUmakefile                |   7 -
 src/admin/ladmin.cpp                 |  17 +-
 src/admin/main.cpp                   |   2 +
 src/char/GNUmakefile                 |   7 -
 src/char/char.cpp                    |  19 +-
 src/char/char.hpp                    |  13 +-
 src/char/int_party.cpp               |   9 +-
 src/char/int_party.hpp               |   6 +-
 src/char/int_storage.cpp             |   9 +-
 src/char/int_storage.hpp             |   6 +-
 src/char/inter.cpp                   |  13 +-
 src/char/inter.hpp                   |   6 +-
 src/char/main.cpp                    |   2 +
 src/common/GNUmakefile               |   7 -
 src/common/config_parse.cpp          | 154 ------------
 src/common/config_parse.hpp          |  36 ---
 src/common/const_array.hpp           | 132 ----------
 src/common/core.cpp                  | 124 ---------
 src/common/core.hpp                  |  22 --
 src/common/db.cpp                    |   3 -
 src/common/db.hpp                    | 180 -------------
 src/common/dumb_ptr.hpp              | 276 --------------------
 src/common/extract.cpp               |  62 -----
 src/common/extract.hpp               | 223 ----------------
 src/common/extract_test.cpp          | 334 ------------------------
 src/common/human_time_diff.hpp       |  86 -------
 src/common/human_time_diff_test.cpp  |  83 ------
 src/common/intern-pool.hpp           |  41 ---
 src/common/intern-pool_test.cpp      |  20 --
 src/common/ip.cpp                    | 114 ---------
 src/common/ip.hpp                    | 164 ------------
 src/common/ip.py                     |  14 --
 src/common/ip_test.cpp               | 332 ------------------------
 src/common/iter.hpp                  |  97 -------
 src/common/iter_test.cpp             |  82 ------
 src/common/matrix.hpp                |  50 ----
 src/common/md5calc.cpp               | 352 --------------------------
 src/common/md5calc.hpp               |  64 -----
 src/common/md5calc_test.cpp          |  30 ---
 src/common/mmo.hpp                   | 380 ----------------------------
 src/common/nullpo.cpp                |  28 ---
 src/common/nullpo.hpp                |  41 ---
 src/common/operators.hpp             |  47 ----
 src/common/random.cpp                |   8 -
 src/common/random.hpp                |  69 -----
 src/common/random.t.hpp              |  23 --
 src/common/random2.hpp               |  74 ------
 src/common/socket.cpp                | 474 -----------------------------------
 src/common/socket.hpp                | 371 ---------------------------
 src/common/timer.cpp                 | 201 ---------------
 src/common/timer.hpp                 |  30 ---
 src/common/timer.t.hpp               |  68 -----
 src/common/utils.cpp                 | 101 --------
 src/common/utils.hpp                 | 140 -----------
 src/common/utils2.hpp                | 256 -------------------
 src/common/version.cpp               |  53 ----
 src/common/version.hpp               |  69 -----
 src/compat/alg.cpp                   |   3 +
 src/compat/alg.hpp                   |  21 ++
 src/compat/attr.hpp                  |  13 +
 src/compat/cast.cpp                  |   3 +
 src/compat/cast.hpp                  |  54 ++++
 src/compat/fun.hpp                   |  11 +
 src/compat/iter.cpp                  |   3 +
 src/compat/iter.hpp                  |  97 +++++++
 src/compat/iter_test.cpp             |  82 ++++++
 src/compat/memory.cpp                |   3 +
 src/compat/memory.hpp                |  27 ++
 src/compat/nullpo.cpp                |  28 +++
 src/compat/nullpo.hpp                |  41 +++
 src/compat/rawmem.cpp                |   3 +
 src/compat/rawmem.hpp                |  30 +++
 src/conf/version.hpp                 |   6 +-
 src/generic/const_array.cpp          |   3 +
 src/generic/const_array.hpp          | 132 ++++++++++
 src/generic/db.cpp                   |   3 +
 src/generic/db.hpp                   | 180 +++++++++++++
 src/generic/enum.cpp                 |   3 +
 src/generic/enum.hpp                 | 170 +++++++++++++
 src/generic/intern-pool.cpp          |   3 +
 src/generic/intern-pool.hpp          |  41 +++
 src/generic/intern-pool_test.cpp     |  20 ++
 src/generic/matrix.cpp               |   3 +
 src/generic/matrix.hpp               |  54 ++++
 src/generic/md5.cpp                  | 234 +++++++++++++++++
 src/generic/md5.hpp                  |  44 ++++
 src/generic/md5_test.cpp             |  28 +++
 src/generic/operators.cpp            |   3 +
 src/generic/operators.hpp            |  47 ++++
 src/generic/random.cpp               |   8 +
 src/generic/random.hpp               |  69 +++++
 src/generic/random.t.hpp             |  23 ++
 src/generic/random2.hpp              |  75 ++++++
 src/io/cxxstdio.cpp                  |   2 +
 src/io/cxxstdio.hpp                  |  61 +++--
 src/io/fd.cpp                        |   1 +
 src/io/fd.hpp                        |   2 +-
 src/io/fwd.hpp                       |   2 +-
 src/io/line.hpp                      |   2 +-
 src/io/read.hpp                      |   2 +-
 src/io/tty.cpp                       |   2 +
 src/io/tty.hpp                       |   2 +-
 src/io/write.hpp                     |   2 +-
 src/login/GNUmakefile                |   7 -
 src/login/login.cpp                  |  25 +-
 src/login/main.cpp                   |   2 +
 src/map/GNUmakefile                  |   7 -
 src/map/atcommand.cpp                |  24 +-
 src/map/atcommand.hpp                |   8 +-
 src/map/battle.cpp                   |   9 +-
 src/map/battle.hpp                   |   8 +-
 src/map/battle.t.hpp                 |   8 +-
 src/map/chrif.cpp                    |  10 +-
 src/map/chrif.hpp                    |  12 +-
 src/map/clif.cpp                     |  17 +-
 src/map/clif.hpp                     |  13 +-
 src/map/clif.t.hpp                   |   6 +-
 src/map/grfio.cpp                    |   2 +-
 src/map/grfio.hpp                    |   8 +-
 src/map/intif.cpp                    |   5 +-
 src/map/intif.hpp                    |   8 +-
 src/map/itemdb.cpp                   |  14 +-
 src/map/itemdb.hpp                   |   8 +-
 src/map/magic-expr-eval.hpp          |   8 +-
 src/map/magic-expr.cpp               |   6 +-
 src/map/magic-expr.hpp               |   6 +-
 src/map/magic-interpreter-aux.hpp    |   6 +-
 src/map/magic-interpreter-base.cpp   |   2 +-
 src/map/magic-interpreter-parser.ypp |   4 +-
 src/map/magic-interpreter.hpp        |   6 +-
 src/map/magic-interpreter.t.hpp      |   8 +-
 src/map/magic-stmt.cpp               |   8 +-
 src/map/magic.hpp                    |   8 +-
 src/map/main.cpp                     |   2 +
 src/map/map.cpp                      |  21 +-
 src/map/map.hpp                      |  15 +-
 src/map/map.t.hpp                    |   9 +-
 src/map/mapflag.hpp                  |   2 +-
 src/map/mob.cpp                      |  15 +-
 src/map/mob.hpp                      |  13 +-
 src/map/mob.t.hpp                    |   6 +-
 src/map/npc.cpp                      |  15 +-
 src/map/npc.hpp                      |   8 +-
 src/map/party.cpp                    |  10 +-
 src/map/party.hpp                    |   6 +-
 src/map/path.cpp                     |   7 +-
 src/map/path.hpp                     |   6 +-
 src/map/pc.cpp                       |  12 +-
 src/map/pc.hpp                       |   6 +-
 src/map/pc.t.hpp                     |   6 +-
 src/map/script.cpp                   |  21 +-
 src/map/script.hpp                   |  13 +-
 src/map/script.py                    |   3 +
 src/map/skill.cpp                    |  14 +-
 src/map/skill.hpp                    |   6 +-
 src/map/skill.t.hpp                  |   8 +-
 src/map/storage.cpp                  |   8 +-
 src/map/storage.hpp                  |   8 +-
 src/map/tmw.cpp                      |   4 +-
 src/map/tmw.hpp                      |  11 +-
 src/map/trade.cpp                    |   4 +-
 src/map/trade.hpp                    |   6 +-
 src/mmo/config_parse.cpp             | 154 ++++++++++++
 src/mmo/config_parse.hpp             |  36 +++
 src/mmo/core.cpp                     | 125 +++++++++
 src/mmo/core.hpp                     |  22 ++
 src/mmo/dumb_ptr.cpp                 |   3 +
 src/mmo/dumb_ptr.hpp                 | 276 ++++++++++++++++++++
 src/mmo/extract.cpp                  |  62 +++++
 src/mmo/extract.hpp                  | 224 +++++++++++++++++
 src/mmo/extract_test.cpp             | 334 ++++++++++++++++++++++++
 src/mmo/human_time_diff.cpp          |   3 +
 src/mmo/human_time_diff.hpp          |  86 +++++++
 src/mmo/human_time_diff_test.cpp     |  83 ++++++
 src/mmo/ip.cpp                       | 114 +++++++++
 src/mmo/ip.hpp                       | 164 ++++++++++++
 src/mmo/ip.py                        |  14 ++
 src/mmo/ip_test.cpp                  | 332 ++++++++++++++++++++++++
 src/mmo/md5more.cpp                  | 128 ++++++++++
 src/mmo/md5more.hpp                  |  26 ++
 src/mmo/mmo.cpp                      |   3 +
 src/mmo/mmo.hpp                      | 377 ++++++++++++++++++++++++++++
 src/mmo/socket.cpp                   | 474 +++++++++++++++++++++++++++++++++++
 src/mmo/socket.hpp                   | 373 +++++++++++++++++++++++++++
 src/mmo/timer.cpp                    | 201 +++++++++++++++
 src/mmo/timer.hpp                    |  30 +++
 src/mmo/timer.t.hpp                  |  68 +++++
 src/mmo/utils.cpp                    | 101 ++++++++
 src/mmo/utils.hpp                    | 116 +++++++++
 src/mmo/version.cpp                  |  55 ++++
 src/mmo/version.hpp                  |  69 +++++
 src/monitor/GNUmakefile              |   7 -
 src/monitor/main.cpp                 |   4 +-
 src/sanity.hpp                       |   5 -
 src/strings/astring.cpp              |   2 +
 src/strings/base.hpp                 |   2 +
 src/strings/fwd.hpp                  |   2 +
 src/strings/mstring.cpp              |   2 +
 src/strings/rstring.cpp              |   2 +
 src/strings/sstring.cpp              |   2 +
 src/strings/tstring.cpp              |   2 +
 src/strings/vstring.cpp              |   2 +
 src/strings/vstring.hpp              |   8 +
 src/strings/vstring.tcc              |   2 +-
 src/strings/xstring.cpp              |   2 +
 src/strings/zstring.cpp              |   2 +
 206 files changed, 6030 insertions(+), 5874 deletions(-)
 delete mode 100644 src/admin/GNUmakefile
 delete mode 100644 src/char/GNUmakefile
 delete mode 100644 src/common/GNUmakefile
 delete mode 100644 src/common/config_parse.cpp
 delete mode 100644 src/common/config_parse.hpp
 delete mode 100644 src/common/const_array.hpp
 delete mode 100644 src/common/core.cpp
 delete mode 100644 src/common/core.hpp
 delete mode 100644 src/common/db.cpp
 delete mode 100644 src/common/db.hpp
 delete mode 100644 src/common/dumb_ptr.hpp
 delete mode 100644 src/common/extract.cpp
 delete mode 100644 src/common/extract.hpp
 delete mode 100644 src/common/extract_test.cpp
 delete mode 100644 src/common/human_time_diff.hpp
 delete mode 100644 src/common/human_time_diff_test.cpp
 delete mode 100644 src/common/intern-pool.hpp
 delete mode 100644 src/common/intern-pool_test.cpp
 delete mode 100644 src/common/ip.cpp
 delete mode 100644 src/common/ip.hpp
 delete mode 100644 src/common/ip.py
 delete mode 100644 src/common/ip_test.cpp
 delete mode 100644 src/common/iter.hpp
 delete mode 100644 src/common/iter_test.cpp
 delete mode 100644 src/common/matrix.hpp
 delete mode 100644 src/common/md5calc.cpp
 delete mode 100644 src/common/md5calc.hpp
 delete mode 100644 src/common/md5calc_test.cpp
 delete mode 100644 src/common/mmo.hpp
 delete mode 100644 src/common/nullpo.cpp
 delete mode 100644 src/common/nullpo.hpp
 delete mode 100644 src/common/operators.hpp
 delete mode 100644 src/common/random.cpp
 delete mode 100644 src/common/random.hpp
 delete mode 100644 src/common/random.t.hpp
 delete mode 100644 src/common/random2.hpp
 delete mode 100644 src/common/socket.cpp
 delete mode 100644 src/common/socket.hpp
 delete mode 100644 src/common/timer.cpp
 delete mode 100644 src/common/timer.hpp
 delete mode 100644 src/common/timer.t.hpp
 delete mode 100644 src/common/utils.cpp
 delete mode 100644 src/common/utils.hpp
 delete mode 100644 src/common/utils2.hpp
 delete mode 100644 src/common/version.cpp
 delete mode 100644 src/common/version.hpp
 create mode 100644 src/compat/alg.cpp
 create mode 100644 src/compat/alg.hpp
 create mode 100644 src/compat/attr.hpp
 create mode 100644 src/compat/cast.cpp
 create mode 100644 src/compat/cast.hpp
 create mode 100644 src/compat/fun.hpp
 create mode 100644 src/compat/iter.cpp
 create mode 100644 src/compat/iter.hpp
 create mode 100644 src/compat/iter_test.cpp
 create mode 100644 src/compat/memory.cpp
 create mode 100644 src/compat/memory.hpp
 create mode 100644 src/compat/nullpo.cpp
 create mode 100644 src/compat/nullpo.hpp
 create mode 100644 src/compat/rawmem.cpp
 create mode 100644 src/compat/rawmem.hpp
 create mode 100644 src/generic/const_array.cpp
 create mode 100644 src/generic/const_array.hpp
 create mode 100644 src/generic/db.cpp
 create mode 100644 src/generic/db.hpp
 create mode 100644 src/generic/enum.cpp
 create mode 100644 src/generic/enum.hpp
 create mode 100644 src/generic/intern-pool.cpp
 create mode 100644 src/generic/intern-pool.hpp
 create mode 100644 src/generic/intern-pool_test.cpp
 create mode 100644 src/generic/matrix.cpp
 create mode 100644 src/generic/matrix.hpp
 create mode 100644 src/generic/md5.cpp
 create mode 100644 src/generic/md5.hpp
 create mode 100644 src/generic/md5_test.cpp
 create mode 100644 src/generic/operators.cpp
 create mode 100644 src/generic/operators.hpp
 create mode 100644 src/generic/random.cpp
 create mode 100644 src/generic/random.hpp
 create mode 100644 src/generic/random.t.hpp
 create mode 100644 src/generic/random2.hpp
 delete mode 100644 src/login/GNUmakefile
 delete mode 100644 src/map/GNUmakefile
 create mode 100644 src/mmo/config_parse.cpp
 create mode 100644 src/mmo/config_parse.hpp
 create mode 100644 src/mmo/core.cpp
 create mode 100644 src/mmo/core.hpp
 create mode 100644 src/mmo/dumb_ptr.cpp
 create mode 100644 src/mmo/dumb_ptr.hpp
 create mode 100644 src/mmo/extract.cpp
 create mode 100644 src/mmo/extract.hpp
 create mode 100644 src/mmo/extract_test.cpp
 create mode 100644 src/mmo/human_time_diff.cpp
 create mode 100644 src/mmo/human_time_diff.hpp
 create mode 100644 src/mmo/human_time_diff_test.cpp
 create mode 100644 src/mmo/ip.cpp
 create mode 100644 src/mmo/ip.hpp
 create mode 100644 src/mmo/ip.py
 create mode 100644 src/mmo/ip_test.cpp
 create mode 100644 src/mmo/md5more.cpp
 create mode 100644 src/mmo/md5more.hpp
 create mode 100644 src/mmo/mmo.cpp
 create mode 100644 src/mmo/mmo.hpp
 create mode 100644 src/mmo/socket.cpp
 create mode 100644 src/mmo/socket.hpp
 create mode 100644 src/mmo/timer.cpp
 create mode 100644 src/mmo/timer.hpp
 create mode 100644 src/mmo/timer.t.hpp
 create mode 100644 src/mmo/utils.cpp
 create mode 100644 src/mmo/utils.hpp
 create mode 100644 src/mmo/version.cpp
 create mode 100644 src/mmo/version.hpp
 delete mode 100644 src/monitor/GNUmakefile

diff --git a/src/admin/GNUmakefile b/src/admin/GNUmakefile
deleted file mode 100644
index a34b76b..0000000
--- a/src/admin/GNUmakefile
+++ /dev/null
@@ -1,7 +0,0 @@
-.SUFFIXES:
-bin/tmwa-admin:
-	${MAKE} -C ../.. bin/tmwa-admin
-clean:
-	rm -r ../../obj/admin/
-%::
-	${MAKE} -C ../.. obj/admin/$@
diff --git a/src/admin/ladmin.cpp b/src/admin/ladmin.cpp
index 38f0c4b..f889b19 100644
--- a/src/admin/ladmin.cpp
+++ b/src/admin/ladmin.cpp
@@ -11,19 +11,20 @@
 #include "../strings/xstring.hpp"
 #include "../strings/vstring.hpp"
 
+#include "../generic/md5.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 #include "../io/tty.hpp"
 #include "../io/write.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/core.hpp"
-#include "../common/human_time_diff.hpp"
-#include "../common/md5calc.hpp"
-#include "../common/mmo.hpp"
-#include "../common/socket.hpp"
-#include "../common/utils.hpp"
-#include "../common/version.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/core.hpp"
+#include "../mmo/human_time_diff.hpp"
+#include "../mmo/mmo.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/utils.hpp"
+#include "../mmo/version.hpp"
 
 #include "../poison.hpp"
 
diff --git a/src/admin/main.cpp b/src/admin/main.cpp
index 2900720..bed1539 100644
--- a/src/admin/main.cpp
+++ b/src/admin/main.cpp
@@ -1,2 +1,4 @@
 // dummy file to make Make dependencies work
 #include "ladmin.hpp"
+
+#include "../poison.hpp"
diff --git a/src/char/GNUmakefile b/src/char/GNUmakefile
deleted file mode 100644
index 48b4c0a..0000000
--- a/src/char/GNUmakefile
+++ /dev/null
@@ -1,7 +0,0 @@
-.SUFFIXES:
-bin/tmwa-char:
-	${MAKE} -C ../.. bin/tmwa-char
-clean:
-	rm -r ../../obj/char/
-%::
-	${MAKE} -C ../.. obj/char/$@
diff --git a/src/char/char.cpp b/src/char/char.cpp
index f6fd492..b74df46 100644
--- a/src/char/char.cpp
+++ b/src/char/char.cpp
@@ -17,24 +17,27 @@
 #include <bitset>
 #include <set>
 
+#include "../compat/alg.hpp"
+
 #include "../strings/mstring.hpp"
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/lock.hpp"
 #include "../io/read.hpp"
 #include "../io/tty.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/core.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/human_time_diff.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/version.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/core.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/human_time_diff.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/version.hpp"
 
 #include "inter.hpp"
 #include "int_party.hpp"
diff --git a/src/char/char.hpp b/src/char/char.hpp
index 57cacee..6c47c1a 100644
--- a/src/char/char.hpp
+++ b/src/char/char.hpp
@@ -1,11 +1,12 @@
-#ifndef CHAR_HPP
-#define CHAR_HPP
+#ifndef TMWA_CHAR_CHAR_HPP
+#define TMWA_CHAR_CHAR_HPP
 
 # include "../strings/fwd.hpp"
 
-# include "../common/const_array.hpp"
-# include "../common/ip.hpp"
-# include "../common/mmo.hpp"
+# include "../generic/const_array.hpp"
+
+# include "../mmo/ip.hpp"
+# include "../mmo/mmo.hpp"
 
 class Session;
 
@@ -32,4 +33,4 @@ void char_log(XString line);
 # define CHAR_LOG(fmt, ...) \
     char_log(STRPRINTF(fmt, ## __VA_ARGS__))
 
-#endif // CHAR_HPP
+#endif // TMWA_CHAR_CHAR_HPP
diff --git a/src/char/int_party.cpp b/src/char/int_party.cpp
index 85af96c..c0bedde 100644
--- a/src/char/int_party.cpp
+++ b/src/char/int_party.cpp
@@ -7,14 +7,15 @@
 #include "../strings/astring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/lock.hpp"
 #include "../io/read.hpp"
 
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/mmo.hpp"
-#include "../common/socket.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/mmo.hpp"
+#include "../mmo/socket.hpp"
 
 #include "char.hpp"
 #include "inter.hpp"
diff --git a/src/char/int_party.hpp b/src/char/int_party.hpp
index e4dedd1..73020a9 100644
--- a/src/char/int_party.hpp
+++ b/src/char/int_party.hpp
@@ -1,5 +1,5 @@
-#ifndef INT_PARTY_HPP
-#define INT_PARTY_HPP
+#ifndef TMWA_CHAR_INT_PARTY_HPP
+#define TMWA_CHAR_INT_PARTY_HPP
 
 # include "../strings/fwd.hpp"
 
@@ -14,4 +14,4 @@ void inter_party_leave(int party_id, int account_id);
 
 extern AString party_txt;
 
-#endif // INT_PARTY_HPP
+#endif // TMWA_CHAR_INT_PARTY_HPP
diff --git a/src/char/int_storage.cpp b/src/char/int_storage.cpp
index 91151ec..0794c8f 100644
--- a/src/char/int_storage.cpp
+++ b/src/char/int_storage.cpp
@@ -9,14 +9,15 @@
 #include "../strings/astring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/lock.hpp"
 #include "../io/read.hpp"
 
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/mmo.hpp"
-#include "../common/socket.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/mmo.hpp"
+#include "../mmo/socket.hpp"
 
 #include "../poison.hpp"
 
diff --git a/src/char/int_storage.hpp b/src/char/int_storage.hpp
index 7081f30..356d924 100644
--- a/src/char/int_storage.hpp
+++ b/src/char/int_storage.hpp
@@ -1,5 +1,5 @@
-#ifndef INT_STORAGE_HPP
-#define INT_STORAGE_HPP
+#ifndef TMWA_CHAR_INT_STORAGE_HPP
+#define TMWA_CHAR_INT_STORAGE_HPP
 
 # include "../strings/fwd.hpp"
 
@@ -14,4 +14,4 @@ int inter_storage_parse_frommap(Session *ms);
 
 extern AString storage_txt;
 
-#endif // INT_STORAGE_HPP
+#endif // TMWA_CHAR_INT_STORAGE_HPP
diff --git a/src/char/inter.cpp b/src/char/inter.cpp
index 4fea6be..c287844 100644
--- a/src/char/inter.cpp
+++ b/src/char/inter.cpp
@@ -11,16 +11,17 @@
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/lock.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/utils.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/utils.hpp"
 
 #include "char.hpp"
 #include "int_party.hpp"
diff --git a/src/char/inter.hpp b/src/char/inter.hpp
index b566575..6dd907d 100644
--- a/src/char/inter.hpp
+++ b/src/char/inter.hpp
@@ -1,5 +1,5 @@
-#ifndef INTER_HPP
-#define INTER_HPP
+#ifndef TMWA_CHAR_INTER_HPP
+#define TMWA_CHAR_INTER_HPP
 
 # include "../strings/fwd.hpp"
 
@@ -14,4 +14,4 @@ int inter_check_length(Session *ms, int length);
 
 extern int party_share_level;
 
-#endif // INTER_HPP
+#endif // TMWA_CHAR_INTER_HPP
diff --git a/src/char/main.cpp b/src/char/main.cpp
index 9f68e0f..de51ae3 100644
--- a/src/char/main.cpp
+++ b/src/char/main.cpp
@@ -1,2 +1,4 @@
 // dummy file to make Make dependencies work
 #include "char.hpp"
+
+#include "../poison.hpp"
diff --git a/src/common/GNUmakefile b/src/common/GNUmakefile
deleted file mode 100644
index 917ce0e..0000000
--- a/src/common/GNUmakefile
+++ /dev/null
@@ -1,7 +0,0 @@
-.SUFFIXES:
-common:
-	${MAKE} -C ../.. common
-clean:
-	rm -r ../../obj/common/
-%::
-	${MAKE} -C ../.. obj/common/$@
diff --git a/src/common/config_parse.cpp b/src/common/config_parse.cpp
deleted file mode 100644
index d677d8e..0000000
--- a/src/common/config_parse.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-#include "config_parse.hpp"
-//    config_parse.hpp - Framework for per-server config parsers.
-//
-//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-#include "../strings/xstring.hpp"
-#include "../strings/zstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-#include "../io/line.hpp"
-
-#include "version.hpp"
-
-#include "../poison.hpp"
-
-bool is_comment(XString line)
-{
-    return not line or line.startswith("//");
-}
-
-template<class ZS>
-inline
-bool config_split_impl(ZS line, XString *key, ZS *value)
-{
-    // unconditionally fail if line contains control characters
-    if (std::find_if(line.begin(), line.end(),
-                [](unsigned char c) { return c < ' '; }
-                ) != line.end())
-        return false;
-    // try to find a colon, fail if not found
-    typename ZS::iterator colon = std::find(line.begin(), line.end(), ':');
-    if (colon == line.end())
-        return false;
-
-    *key = line.xislice_h(colon).strip();
-    // move past the colon and any spaces
-    ++colon;
-    *value = line.xislice_t(colon).lstrip();
-    return true;
-}
-
-// eventually this should go away
-bool config_split(ZString line, XString *key, ZString *value)
-{
-    return config_split_impl(line, key, value);
-}
-// and use this instead
-bool config_split(XString line, XString *key, XString *value)
-{
-    return config_split_impl(line, key, value);
-}
-
-/// Master config parser. This handles 'import' and 'version-ge' etc.
-/// Then it defers to the inferior parser for a line it does not understand.
-bool load_config_file(ZString filename, ConfigItemParser slave)
-{
-    io::LineReader in(filename);
-    if (!in.is_open())
-    {
-        PRINTF("Unable to open file: %s\n", filename);
-        return false;
-    }
-    io::Line line;
-    bool rv = true;
-    while (in.read_line(line))
-    {
-        if (is_comment(line.text))
-            continue;
-        XString key;
-        ZString value;
-        if (!config_split(line.text, &key, &value))
-        {
-            line.error("Bad config line");
-            rv = false;
-            continue;
-        }
-        if (key == "import")
-        {
-            rv &= load_config_file(value, slave);
-            continue;
-        }
-        else if (key == "version-lt")
-        {
-            Version vers;
-            if (!extract(value, &vers))
-            {
-                rv = false;
-                continue;
-            }
-            if (CURRENT_VERSION < vers)
-                continue;
-            break;
-        }
-        else if (key == "version-le")
-        {
-            Version vers;
-            if (!extract(value, &vers))
-            {
-                rv = false;
-                continue;
-            }
-            if (CURRENT_VERSION <= vers)
-                continue;
-            break;
-        }
-        else if (key == "version-gt")
-        {
-            Version vers;
-            if (!extract(value, &vers))
-            {
-                rv = false;
-                continue;
-            }
-            if (CURRENT_VERSION > vers)
-                continue;
-            break;
-        }
-        else if (key == "version-ge")
-        {
-            Version vers;
-            if (!extract(value, &vers))
-            {
-                rv = false;
-                continue;
-            }
-            if (CURRENT_VERSION >= vers)
-                continue;
-            break;
-        }
-        else if (!slave(key, value))
-        {
-            line.error("Bad config key or value");
-            rv = false;
-            continue;
-        }
-        // nothing to see here, move along
-    }
-    return rv;
-}
diff --git a/src/common/config_parse.hpp b/src/common/config_parse.hpp
deleted file mode 100644
index 6a55174..0000000
--- a/src/common/config_parse.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef TMWA_COMMON_CONFIG_PARSE_HPP
-#define TMWA_COMMON_CONFIG_PARSE_HPP
-//    config_parse.hpp - Framework for per-server config parsers.
-//
-//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include "../strings/fwd.hpp"
-
-typedef bool (*ConfigItemParser)(XString key, ZString value);
-
-bool is_comment(XString line);
-bool config_split(ZString line, XString *key, ZString *value);
-bool config_split(XString line, XString *key, XString *value);
-
-/// Master config parser. This handles 'import' and 'version-ge' etc.
-/// Then it defers to the inferior parser for a line it does not understand.
-bool load_config_file(ZString filename, ConfigItemParser slave);
-
-#endif // TMWA_COMMON_CONFIG_PARSE_HPP
diff --git a/src/common/const_array.hpp b/src/common/const_array.hpp
deleted file mode 100644
index fe15728..0000000
--- a/src/common/const_array.hpp
+++ /dev/null
@@ -1,132 +0,0 @@
-#ifndef CONST_ARRAY_HPP
-#define CONST_ARRAY_HPP
-//    const_array.hpp - just a pointer-to-const and a length
-//
-//    Copyright © 2011-2012 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include <cstring>
-
-# include <iterator>
-# include <ostream>
-# include <vector>
-
-# ifdef WORKAROUND_GCC46_COMPILER
-// constexpr is buggy with templates in this version
-// Is this still needed now that const_string is removed?
-#  define constexpr /* nothing */
-# endif
-
-// TODO see if I ever actually use this, and not the subclass
-template<class T>
-class const_array
-{
-    const T *d;
-    size_t n;
-public:
-    typedef const T *iterator;
-    typedef std::reverse_iterator<iterator> reverse_iterator;
-
-    constexpr
-    const_array(std::nullptr_t)
-    : d(nullptr), n(0)
-    {}
-
-    constexpr
-    const_array(const T *p, size_t z)
-    : d(p), n(z)
-    {}
-
-    constexpr
-    const_array(const T *b, const T *e)
-    : d(b), n(e - b)
-    {}
-
-    const_array(std::initializer_list<T> list)
-    : d(list.begin()), n(list.size())
-    {}
-
-    // Implicit conversion from std::vector
-    const_array(const std::vector<T>& v)
-    : d(v.data()), n(v.size())
-    {}
-
-    // but disallow conversion from a temporary
-    const_array(std::vector<T>&&) = delete;
-
-    // Oops. see src/warnings.hpp
-    constexpr
-    const T *data() const { return d; }
-    constexpr
-    size_t size() const { return n; }
-    constexpr
-    bool empty() const { return not n; }
-    constexpr explicit
-    operator bool() const { return n; }
-
-    constexpr
-    std::pair<const_array, const_array> cut(size_t o) const
-    {
-        return {const_array(d, o), const_array(d + o, n - o)};
-    }
-
-    constexpr
-    const_array first(size_t o) const
-    {
-        return cut(o).first;
-    }
-
-    constexpr
-    const_array last(size_t l) const
-    {
-        return cut(size() - l).second;
-    }
-
-    constexpr
-    const_array after(size_t o) const
-    {
-        return cut(o).second;
-    }
-
-    constexpr
-    iterator begin() const { return d; }
-    constexpr
-    iterator end() const { return d + n; }
-    constexpr
-    reverse_iterator rbegin() const { return reverse_iterator(end()); }
-    constexpr
-    reverse_iterator rend() const { return reverse_iterator(begin()); }
-
-    constexpr
-    const T& front() const { return *begin(); }
-    constexpr
-    const T& back() const { return *rbegin(); }
-
-    // This probably shouldn't be used, but I'm adding it for porting.
-    T& operator[](size_t i)
-    {
-        return const_cast<T&>(d[i]);
-    }
-};
-
-# ifdef WORKAROUND_GCC46_COMPILER
-#  undef constexpr
-# endif
-
-#endif // CONST_ARRAY_HPP
diff --git a/src/common/core.cpp b/src/common/core.cpp
deleted file mode 100644
index d57e970..0000000
--- a/src/common/core.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-#include "core.hpp"
-
-#include <sys/wait.h>
-
-#include <unistd.h>
-
-#include <csignal>
-#include <cstdlib>
-#include <ctime>
-
-#include "../strings/zstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "random.hpp"
-#include "socket.hpp"
-#include "timer.hpp"
-
-#include "../poison.hpp"
-
-// Added by Gabuzomeu
-//
-// This is an implementation of signal() using sigaction() for portability.
-// (sigaction() is POSIX; signal() is not.)  Taken from Stevens' _Advanced
-// Programming in the UNIX Environment_.
-//
-typedef void(*sigfunc)(int);
-static
-sigfunc compat_signal(int signo, sigfunc func)
-{
-    struct sigaction sact, oact;
-
-    sact.sa_handler = func;
-    sigfillset(&sact.sa_mask);
-    sigdelset(&sact.sa_mask, SIGSEGV);
-    sigdelset(&sact.sa_mask, SIGBUS);
-    sigdelset(&sact.sa_mask, SIGTRAP);
-    sigdelset(&sact.sa_mask, SIGILL);
-    sigdelset(&sact.sa_mask, SIGFPE);
-    sact.sa_flags = 0;
-
-    if (sigaction(signo, &sact, &oact) < 0)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-        return SIG_ERR;
-#pragma GCC diagnostic pop
-
-    return oact.sa_handler;
-}
-
-volatile
-bool runflag = true;
-
-static
-void chld_proc(int)
-{
-    wait(NULL);
-}
-static
-void sig_proc(int)
-{
-    for (int i = 1; i < 31; ++i)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-        compat_signal(i, SIG_IGN);
-#pragma GCC diagnostic pop
-    runflag = false;
-}
-
-/*
-    Note about fatal signals:
-
-    Under certain circumstances,
-    the following signals MUST not be ignored:
-    SIGFPE, SIGSEGV, SIGILL
-    Unless you use SA_SIGINFO and *carefully* check the origin,
-    that means they must be SIG_DFL.
- */
-int main(int argc, char **argv)
-{
-    // ZString args[argc]; is (deliberately!) not supported by clang yet
-    ZString *args = static_cast<ZString *>(alloca(argc * sizeof(ZString)));
-    for (int i = 0; i < argc; ++i)
-        args[i] = ZString(strings::really_construct_from_a_pointer, argv[i], nullptr);
-    do_init(argc, args);
-
-    if (!runflag)
-    {
-        PRINTF("Fatal error during startup; exiting\n");
-        return 1;
-    }
-    // set up exit handlers *after* the initialization has happened.
-    // This is because term_func is likely to depend on successful init.
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-    compat_signal(SIGPIPE, SIG_IGN);
-#pragma GCC diagnostic pop
-    compat_signal(SIGTERM, sig_proc);
-    compat_signal(SIGINT, sig_proc);
-    compat_signal(SIGCHLD, chld_proc);
-
-    // Signal to create coredumps by system when necessary (crash)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-    compat_signal(SIGSEGV, SIG_DFL);
-    compat_signal(SIGBUS, SIG_DFL);
-    compat_signal(SIGTRAP, SIG_DFL);
-    compat_signal(SIGILL, SIG_DFL);
-    compat_signal(SIGFPE, SIG_DFL);
-#pragma GCC diagnostic pop
-
-    atexit(term_func);
-
-    while (runflag)
-    {
-        // TODO - if timers take a long time to run, this
-        // may wait too long in sendrecv
-        tick_t now = milli_clock::now();
-        interval_t next = do_timer(now);
-        do_sendrecv(next);
-        do_parsepacket();
-    }
-}
diff --git a/src/common/core.hpp b/src/common/core.hpp
deleted file mode 100644
index 8141e9c..0000000
--- a/src/common/core.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef CORE_HPP
-#define CORE_HPP
-
-# include "../sanity.hpp"
-
-# include "../strings/fwd.hpp"
-
-/// core.c contains a server-independent main() function
-/// and then runs a do_sendrecv loop
-
-/// When this is cleared, the server exits gracefully.
-extern volatile bool runflag;
-
-/// This is an external function defined by each server
-/// This function must register stuff for the parse loop
-extern int do_init(int, ZString *);
-
-/// Cleanup function called whenever a signal kills us
-/// or when if we manage to exit() gracefully.
-extern void term_func(void);
-
-#endif // CORE_HPP
diff --git a/src/common/db.cpp b/src/common/db.cpp
deleted file mode 100644
index cc58ad8..0000000
--- a/src/common/db.cpp
+++ /dev/null
@@ -1,3 +0,0 @@
-#include "db.hpp"
-
-#include "../poison.hpp"
diff --git a/src/common/db.hpp b/src/common/db.hpp
deleted file mode 100644
index 0384f1c..0000000
--- a/src/common/db.hpp
+++ /dev/null
@@ -1,180 +0,0 @@
-#ifndef DB_HPP
-#define DB_HPP
-//    db.hpp - convenience wrappers over std::map<K, V>
-//
-//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include <map>
-# include <memory>
-
-template<class K, class V>
-class Map
-{
-    typedef std::map<K, V> Impl;
-
-    Impl impl;
-public:
-    Map() = default;
-    Map(std::initializer_list<std::pair<const K, V>> il)
-    : impl(il)
-    {}
-    typedef typename Impl::iterator iterator;
-    typedef typename Impl::const_iterator const_iterator;
-
-    iterator begin() { return impl.begin(); }
-    iterator end() { return impl.end(); }
-    const_iterator begin() const { return impl.begin(); }
-    const_iterator end() const { return impl.end(); }
-
-    V *search(const K& k)
-    {
-        iterator it = impl.find(k);
-        if (it == impl.end())
-            return nullptr;
-        return &it->second;
-    }
-    const V *search(const K& k) const
-    {
-        const_iterator it = impl.find(k);
-        if (it == impl.end())
-            return nullptr;
-        return &it->second;
-    }
-    void insert(const K& k, V v)
-    {
-        // As far as I can tell, this is the simplest way to
-        // implement move-only insert-with-replacement.
-        iterator it = impl.lower_bound(k);
-        // invariant: if it is valid, it->first >= k
-        if (it != impl.end() && it->first == k)
-            it->second = std::move(v);
-        else
-            it = impl.insert(std::pair<K, V>(std::move(k), std::move(v))).first;
-        return (void)&it->second;
-
-    }
-    V *init(const K& k)
-    {
-        return &impl[k];
-    }
-    void erase(const K& k)
-    {
-        impl.erase(k);
-    }
-    void clear()
-    {
-        impl.clear();
-    }
-    bool empty() const
-    {
-        return impl.empty();
-    }
-    size_t size() const
-    {
-        return impl.size();
-    }
-};
-
-template<class K, class V>
-class DMap
-{
-    typedef Map<K, V> Impl;
-
-    Impl impl;
-public:
-    typedef typename Impl::iterator iterator;
-    typedef typename Impl::const_iterator const_iterator;
-
-    iterator begin() { return impl.begin(); }
-    iterator end() { return impl.end(); }
-    const_iterator begin() const { return impl.begin(); }
-    const_iterator end() const { return impl.end(); }
-
-    // const V& ? with a static default V?
-    V get(const K& k)
-    {
-        V *vp = impl.search(k);
-        return vp ? *vp : V();
-    }
-    void put(const K& k, V v)
-    {
-        if (v == V())
-            impl.erase(k);
-        else
-            impl.insert(k, std::move(v));
-    }
-    void clear()
-    {
-        impl.clear();
-    }
-    bool empty() const
-    {
-        return impl.empty();
-    }
-    size_t size() const
-    {
-        return impl.size();
-    }
-};
-
-template<class K, class V>
-class UPMap
-{
-    typedef std::unique_ptr<V> U;
-    typedef Map<K, U> Impl;
-
-    Impl impl;
-public:
-    typedef typename Impl::iterator iterator;
-    typedef typename Impl::const_iterator const_iterator;
-
-    iterator begin() { return impl.begin(); }
-    iterator end() { return impl.end(); }
-    const_iterator begin() const { return impl.begin(); }
-    const_iterator end() const { return impl.end(); }
-
-    // const V& ? with a static default V?
-    V *get(const K& k)
-    {
-        U *up = impl.search(k);
-        return up ? up->get() : nullptr;
-    }
-    void put(const K& k, U v)
-    {
-        if (!v)
-            impl.erase(k);
-        else
-            impl.insert(k, std::move(v));
-    }
-    void clear()
-    {
-        impl.clear();
-    }
-    bool empty() const
-    {
-        return impl.empty();
-    }
-    size_t size() const
-    {
-        return impl.size();
-    }
-};
-
-#endif // DB_HPP
diff --git a/src/common/dumb_ptr.hpp b/src/common/dumb_ptr.hpp
deleted file mode 100644
index bf97f22..0000000
--- a/src/common/dumb_ptr.hpp
+++ /dev/null
@@ -1,276 +0,0 @@
-#ifndef TMWA_COMMON_DUMB_PTR_HPP
-#define TMWA_COMMON_DUMB_PTR_HPP
-//    ptr.hpp - temporary new/delete wrappers
-//
-//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include <cstring>
-
-# include <algorithm>
-
-# include "../strings/astring.hpp"
-# include "../strings/zstring.hpp"
-# include "../strings/xstring.hpp"
-
-# include "const_array.hpp"
-
-// unmanaged new/delete-able pointer
-// should be replaced by std::unique_ptr<T>
-template<class T>
-class dumb_ptr
-{
-    template<class U>
-    friend class dumb_ptr;
-    T *impl;
-public:
-    explicit
-    dumb_ptr(T *p=nullptr)
-    : impl(p)
-    {}
-    template<class U>
-    dumb_ptr(dumb_ptr<U> p)
-    : impl(p.impl)
-    {}
-    dumb_ptr(std::nullptr_t)
-    : impl(nullptr)
-    {}
-
-    void delete_()
-    {
-        delete impl;
-        *this = nullptr;
-    }
-    template<class... A>
-    void new_(A&&... a)
-    {
-        impl = new T(std::forward<A>(a)...);
-    }
-    template<class... A>
-    static
-    dumb_ptr<T> make(A&&... a)
-    {
-        return dumb_ptr<T>(new T(std::forward<A>(a)...));
-    }
-    dumb_ptr& operator = (std::nullptr_t)
-    {
-        impl = nullptr;
-        return *this;
-    }
-
-    T& operator *() const
-    {
-        return *impl;
-    }
-    T *operator->() const
-    {
-        return impl;
-    }
-
-    explicit
-    operator bool() const
-    {
-        return impl;
-    }
-    bool operator !() const
-    {
-        return !impl;
-    }
-
-    friend bool operator == (dumb_ptr l, dumb_ptr r)
-    {
-        return l.impl == r.impl;
-    }
-    friend bool operator != (dumb_ptr l, dumb_ptr r)
-    {
-        return !(l == r);
-    }
-};
-
-// unmanaged new/delete-able pointer
-// should be replaced by std::unique_ptr<T[]> or std::vector<T>
-template<class T>
-class dumb_ptr<T[]>
-{
-    T *impl;
-    size_t sz;
-public:
-    dumb_ptr() : impl(), sz() {}
-    dumb_ptr(std::nullptr_t)
-    : impl(nullptr), sz(0) {}
-    dumb_ptr(T *p, size_t z)
-    : impl(p)
-    , sz(z)
-    {}
-
-    void delete_()
-    {
-        delete[] impl;
-        *this = nullptr;
-    }
-    void new_(size_t z)
-    {
-        impl = new T[z]();
-        sz = z;
-    }
-    static
-    dumb_ptr<T[]> make(size_t z)
-    {
-        return dumb_ptr<T[]>(new T[z](), z);
-    }
-    dumb_ptr& operator = (std::nullptr_t)
-    {
-        impl = nullptr;
-        sz = 0;
-        return *this;
-    }
-
-    size_t size() const
-    {
-        return sz;
-    }
-    void resize(size_t z)
-    {
-        if (z == sz)
-            return;
-        T *np = new T[z]();
-        // not exception-safe, but we don't have a dtor anyway
-        size_t i = std::min(z, sz);
-        while (i-->0)
-            np[i] = std::move(impl[i]);
-        delete[] impl;
-        impl = np;
-        sz = z;
-    }
-
-    T& operator[](size_t i) const
-    {
-        return impl[i];
-    }
-
-    explicit
-    operator bool() const
-    {
-        return impl;
-    }
-    bool operator !() const
-    {
-        return !impl;
-    }
-
-    friend bool operator == (dumb_ptr l, dumb_ptr r)
-    {
-        return l.impl == r.impl;
-    }
-    friend bool operator != (dumb_ptr l, dumb_ptr r)
-    {
-        return !(l == r);
-    }
-};
-
-struct dumb_string
-{
-    dumb_ptr<char[]> impl;
-
-    dumb_string()
-    : impl()
-    {}
-    dumb_string(char *) = delete;
-    // copy ctor, copy assign, and dtor are all default
-
-    static dumb_string copy(const char *b, const char *e)
-    {
-        dumb_string rv;
-        rv.impl.new_((e - b) + 1);
-        std::copy(b, e, &rv.impl[0]);
-        return rv;
-    }
-    static dumb_string copy(const char *sz)
-    {
-        return dumb_string::copy(sz, sz + strlen(sz));
-    }
-    static dumb_string copys(XString s)
-    {
-        return dumb_string::copy(&*s.begin(), &*s.end());
-    }
-    static
-# ifndef __clang__
-    __attribute__((warning("shouldn't use this - slice instead")))
-# endif
-    dumb_string copyn(const char *sn, size_t n)
-    {
-        return dumb_string::copy(sn, sn + strnlen(sn, n));
-    }
-
-    static
-    dumb_string fake(ZString p)
-    {
-        dumb_string rv;
-        size_t len = p.size();
-        rv.impl = dumb_ptr<char[]>(const_cast<char *>(p.c_str()), len);
-        return rv;
-    }
-
-    dumb_string dup() const
-    {
-        return dumb_string::copy(&impl[0]);
-    }
-    void delete_()
-    {
-        impl.delete_();
-    }
-
-    const char *c_str() const
-    {
-        return &impl[0];
-    }
-
-    operator ZString() const
-    {
-        return ZString(strings::really_construct_from_a_pointer, c_str(), nullptr);
-    }
-
-    AString str() const
-    {
-        return ZString(*this);
-    }
-
-    char& operator[](size_t i) const
-    {
-        return impl[i];
-    }
-
-    explicit
-    operator bool() const
-    {
-        return bool(impl);
-    }
-    bool operator !() const
-    {
-        return !impl;
-    }
-};
-
-inline
-const char *convert_for_printf(dumb_string ds)
-{
-    return ds.c_str();
-}
-
-#endif // TMWA_COMMON_DUMB_PTR_HPP
diff --git a/src/common/extract.cpp b/src/common/extract.cpp
deleted file mode 100644
index 378986d..0000000
--- a/src/common/extract.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#include "extract.hpp"
-//    extract.cpp - a simple, hierarchical, tokenizer
-//
-//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-#include "../strings/astring.hpp"
-#include "../strings/xstring.hpp"
-#include "../strings/vstring.hpp"
-
-#include "../poison.hpp"
-
-bool extract(XString str, XString *rv)
-{
-    *rv = str;
-    return true;
-}
-
-bool extract(XString str, AString *rv)
-{
-    *rv = str;
-    return true;
-}
-
-bool extract(XString str, struct global_reg *var)
-{
-    return extract(str,
-            record<','>(&var->str, &var->value));
-}
-
-bool extract(XString str, struct item *it)
-{
-    XString ignored;
-    return extract(str,
-            record<',', 11>(
-                &it->id,
-                &it->nameid,
-                &it->amount,
-                &it->equip,
-                &ignored,
-                &ignored,
-                &ignored,
-                &ignored,
-                &ignored,
-                &ignored,
-                &ignored,
-                &ignored));
-}
diff --git a/src/common/extract.hpp b/src/common/extract.hpp
deleted file mode 100644
index 31cf111..0000000
--- a/src/common/extract.hpp
+++ /dev/null
@@ -1,223 +0,0 @@
-#ifndef EXTRACT_HPP
-#define EXTRACT_HPP
-//    extract.hpp - a simple, hierarchical, tokenizer
-//
-//    Copyright © 2012-2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include <algorithm>
-
-# include "../strings/xstring.hpp"
-
-# include "const_array.hpp"
-# include "mmo.hpp"
-# include "utils.hpp"
-
-template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && !std::is_same<T, bool>::value>::type>
-bool extract(XString str, T *iv)
-{
-    if (!str || str.size() > 20)
-        return false;
-    if (!((str.front() == '-' && std::is_signed<T>::value)
-            || ('0' <= str.front() && str.front() <= '9')))
-        return false;
-    // needs a NUL, but can't always be given one. TODO VString?
-    char buf[20 + 1];
-    std::copy(str.begin(), str.end(), buf);
-    buf[str.size()] = '\0';
-
-    char *end;
-    errno = 0;
-    if (std::is_signed<T>::value)
-    {
-        long long v = strtoll(buf, &end, 10);
-        if (errno || *end)
-            return false;
-        *iv = v;
-        return *iv == v;
-    }
-    else
-    {
-        unsigned long long v = strtoull(buf, &end, 10);
-        if (errno || *end)
-            return false;
-        *iv = v;
-        return *iv == v;
-    }
-}
-
-inline
-bool extract(XString str, TimeT *tv)
-{
-    return extract(str, &tv->value);
-}
-
-// extra typename=void to workaround some duplicate overload rule
-template<class T, typename=typename std::enable_if<std::is_enum<T>::value>::type, typename=void>
-bool extract(XString str, T *iv)
-{
-    typedef typename underlying_type<T>::type U;
-    U v;
-    // defer to integer version
-    if (!extract(str, &v))
-        return false;
-    // TODO check bounds using enum min/max as in SSCANF
-    *iv = static_cast<T>(v);
-    return true;
-}
-
-bool extract(XString str, XString *rv);
-
-bool extract(XString str, AString *rv);
-
-template<uint8_t N>
-bool extract(XString str, VString<N> *out)
-{
-    if (str.size() > N)
-        return false;
-    *out = str;
-    return true;
-}
-
-template<class T>
-class LStripper
-{
-public:
-    T impl;
-};
-
-template<class T>
-LStripper<T> lstripping(T v)
-{
-    return {v};
-}
-
-template<class T>
-bool extract(XString str, LStripper<T> out)
-{
-    return extract(str.lstrip(), out.impl);
-}
-
-// basically just a std::tuple
-// but it provides its data members publically
-template<char split, int n, class... T>
-class Record;
-template<char split, int n>
-class Record<split, n>
-{
-};
-template<char split, int n, class F, class... R>
-class Record<split, n, F, R...>
-{
-public:
-    F frist;
-    Record<split, n - 1, R...> rest;
-public:
-    Record(F f, R... r)
-    : frist(f), rest(r...)
-    {}
-};
-template<char split, class... T>
-Record<split, sizeof...(T), T...> record(T... t)
-{
-    return Record<split, sizeof...(T), T...>(t...);
-}
-template<char split, int n, class... T>
-Record<split, n, T...> record(T... t)
-{
-    static_assert(0 < n && n < sizeof...(T), "don't be silly");
-    return Record<split, n, T...>(t...);
-}
-
-template<char split, int n>
-bool extract(XString str, Record<split, n>)
-{
-    return !str;
-}
-
-template<char split, int n, class F, class... R>
-bool extract(XString str, Record<split, n, F, R...> rec)
-{
-    XString::iterator s = std::find(str.begin(), str.end(), split);
-    XString::iterator s2 = s;
-    if (s2 != str.end())
-        ++s2;
-    XString head = str.xislice_h(s);
-    XString tail = str.xislice_t(s2);
-    if (s == str.end())
-        return (extract(head, rec.frist) && n <= 1)
-            || (!head && n <= 0);
-
-    return (extract(head, rec.frist) || n <= 0)
-        && extract(tail, rec.rest);
-}
-
-template<char split, class T>
-struct VRecord
-{
-    std::vector<T> *arr;
-};
-
-template<char split, class T>
-VRecord<split, T> vrec(std::vector<T> *arr)
-{
-    return {arr};
-}
-
-template<char split, class T>
-bool extract(XString str, VRecord<split, T> rec)
-{
-    if (!str)
-        return true;
-    XString::iterator s = std::find(str.begin(), str.end(), split);
-    rec.arr->emplace_back();
-    if (s == str.end())
-        return extract(str, &rec.arr->back());
-    return extract(str.xislice_h(s), &rec.arr->back())
-        && extract(str.xislice_t(s + 1), rec);
-}
-
-bool extract(XString str, struct global_reg *var);
-
-bool extract(XString str, struct item *it);
-
-inline
-bool extract(XString str, MapName *m)
-{
-    XString::iterator it = std::find(str.begin(), str.end(), '.');
-    str = str.xislice_h(it);
-    VString<15> tmp;
-    bool rv = extract(str, &tmp);
-    *m = tmp;
-    return rv;
-}
-
-inline
-bool extract(XString str, CharName *out)
-{
-    VString<23> tmp;
-    if (extract(str, &tmp))
-    {
-        *out = CharName(tmp);
-        return true;
-    }
-    return false;
-}
-
-#endif // EXTRACT_HPP
diff --git a/src/common/extract_test.cpp b/src/common/extract_test.cpp
deleted file mode 100644
index 3d0610e..0000000
--- a/src/common/extract_test.cpp
+++ /dev/null
@@ -1,334 +0,0 @@
-#include "extract.hpp"
-
-#include <gtest/gtest.h>
-
-#include "../strings/xstring.hpp"
-
-TEST(extract, record_int)
-{
-    int x, y, z;
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 2 3 4 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 2 3 4", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 3 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 3", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 2 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 2", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract(" ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(0, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("", record<' '>(&x, &y, &z)));
-    EXPECT_EQ(0, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-
-    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 2 3 4", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 3 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 3", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract(" ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(0, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ(0, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-
-    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("1 2 3 4", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 3 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 3", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(3, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 2", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(2, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_TRUE(extract("1", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(1, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract(" ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(0, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-    EXPECT_FALSE(extract("", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ(0, x);
-    EXPECT_EQ(0, y);
-    EXPECT_EQ(0, z);
-    x = y = z = 0;
-}
-
-TEST(extract, record_str)
-{
-    XString x, y, z;
-    x = y = z = "";
-    EXPECT_FALSE(extract("1 2 3 4 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1 2 3 4", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 3 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 3", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1 2", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1 ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract(" ", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("", record<' '>(&x, &y, &z)));
-    EXPECT_EQ("", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-
-    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1 2 3 4", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 3 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 3", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract(" ", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("", record<' ', 2>(&x, &y, &z)));
-    EXPECT_EQ("", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-
-    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_FALSE(extract("1 2 3 4", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 3 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 3", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("3", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 2", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("2", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1 ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("1", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("1", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract(" ", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-    EXPECT_TRUE(extract("", record<' ', 1>(&x, &y, &z)));
-    EXPECT_EQ("", x);
-    EXPECT_EQ("", y);
-    EXPECT_EQ("", z);
-    x = y = z = "";
-}
-
-TEST(extract, mapname)
-{
-    MapName map;
-    EXPECT_TRUE(extract("abc", &map));
-    EXPECT_EQ(map, "abc");
-    EXPECT_TRUE(extract("abc.gat", &map));
-    EXPECT_EQ(map, "abc");
-    EXPECT_TRUE(extract("abcdefghijklmno", &map));
-    EXPECT_EQ(map, "abcdefghijklmno");
-    EXPECT_TRUE(extract("abcdefghijklmno.gat", &map));
-    EXPECT_EQ(map, "abcdefghijklmno");
-}
diff --git a/src/common/human_time_diff.hpp b/src/common/human_time_diff.hpp
deleted file mode 100644
index b8040b8..0000000
--- a/src/common/human_time_diff.hpp
+++ /dev/null
@@ -1,86 +0,0 @@
-#ifndef TMWA_COMMON_HUMAN_TIME_DIFF_HPP
-#define TMWA_COMMON_HUMAN_TIME_DIFF_HPP
-//    human_time_diff.hpp - broken deltas
-//
-//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include "../strings/xstring.hpp"
-
-# include "extract.hpp"
-
-struct HumanTimeDiff
-{
-    short year, month, day, hour, minute, second;
-
-    explicit
-    operator bool()
-    {
-        return year || month || day || hour || minute || second;
-    }
-
-    bool operator !()
-    {
-        return !bool(*this);
-    }
-};
-
-inline
-bool extract(XString str, HumanTimeDiff *iv)
-{
-    // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s)
-    // there are NO spaces here
-    // parse by counting the number starts
-    auto is_num = [](char c)
-    { return c == '-' || c == '+' || ('0' <= c && c <= '9'); };
-    if (!str || !is_num(str.front()))
-        return false;
-    *iv = HumanTimeDiff{};
-    while (str)
-    {
-        auto it = std::find_if_not(str.begin(), str.end(), is_num);
-        auto it2 = std::find_if(it, str.end(), is_num);
-        XString number = str.xislice_h(it);
-        XString suffix = str.xislice(it, it2);
-        str = str.xislice_t(it2);
-
-        short *ptr = nullptr;
-        if (suffix == "y" || suffix == "a")
-            ptr = &iv->year;
-        else if (suffix == "m")
-            ptr = &iv->month;
-        else if (suffix == "j" || suffix == "d")
-            ptr = &iv->day;
-        else if (suffix == "h")
-            ptr = &iv->hour;
-        else if (suffix == "mn")
-            ptr = &iv->minute;
-        else if (suffix == "s")
-            ptr = &iv->second;
-        else
-            return false;
-        if (number.startswith('+') && !number.startswith("+-"))
-            number = number.xslice_t(1);
-        if (*ptr || !extract(number, ptr))
-            return false;
-    }
-    return true;
-}
-
-#endif // TMWA_COMMON_HUMAN_TIME_DIFF_HPP
diff --git a/src/common/human_time_diff_test.cpp b/src/common/human_time_diff_test.cpp
deleted file mode 100644
index d3ddad1..0000000
--- a/src/common/human_time_diff_test.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-#include "human_time_diff.hpp"
-
-#include <gtest/gtest.h>
-
-// a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s)
-
-TEST(humantimediff, single)
-{
-    HumanTimeDiff diff;
-
-    EXPECT_TRUE(extract("42y", &diff));
-    EXPECT_EQ(42, diff.year);
-    EXPECT_EQ(0, diff.month);
-    EXPECT_EQ(0, diff.day);
-    EXPECT_EQ(0, diff.hour);
-    EXPECT_EQ(0, diff.minute);
-    EXPECT_EQ(0, diff.second);
-
-    EXPECT_TRUE(extract("42m", &diff));
-    EXPECT_EQ(0, diff.year);
-    EXPECT_EQ(42, diff.month);
-    EXPECT_EQ(0, diff.day);
-    EXPECT_EQ(0, diff.hour);
-    EXPECT_EQ(0, diff.minute);
-    EXPECT_EQ(0, diff.second);
-
-    EXPECT_TRUE(extract("42d", &diff));
-    EXPECT_EQ(0, diff.year);
-    EXPECT_EQ(0, diff.month);
-    EXPECT_EQ(42, diff.day);
-    EXPECT_EQ(0, diff.hour);
-    EXPECT_EQ(0, diff.minute);
-    EXPECT_EQ(0, diff.second);
-
-    EXPECT_TRUE(extract("42h", &diff));
-    EXPECT_EQ(0, diff.year);
-    EXPECT_EQ(0, diff.month);
-    EXPECT_EQ(0, diff.day);
-    EXPECT_EQ(42, diff.hour);
-    EXPECT_EQ(0, diff.minute);
-    EXPECT_EQ(0, diff.second);
-
-    EXPECT_TRUE(extract("42mn", &diff));
-    EXPECT_EQ(0, diff.year);
-    EXPECT_EQ(0, diff.month);
-    EXPECT_EQ(0, diff.day);
-    EXPECT_EQ(0, diff.hour);
-    EXPECT_EQ(42, diff.minute);
-    EXPECT_EQ(0, diff.second);
-
-    EXPECT_TRUE(extract("42s", &diff));
-    EXPECT_EQ(0, diff.year);
-    EXPECT_EQ(0, diff.month);
-    EXPECT_EQ(0, diff.day);
-    EXPECT_EQ(0, diff.hour);
-    EXPECT_EQ(0, diff.minute);
-    EXPECT_EQ(42, diff.second);
-
-    EXPECT_TRUE(extract("+42y", &diff));
-    EXPECT_EQ(42, diff.year);
-    EXPECT_TRUE(extract("-42y", &diff));
-    EXPECT_EQ(-42, diff.year);
-    EXPECT_FALSE(extract("++42y", &diff));
-    EXPECT_FALSE(extract("+-42y", &diff));
-    EXPECT_FALSE(extract("-+42y", &diff));
-    EXPECT_FALSE(extract("--42y", &diff));
-    EXPECT_FALSE(extract("4+2y", &diff));
-    EXPECT_FALSE(extract("42z", &diff));
-}
-
-TEST(humantimediff, multiple)
-{
-    HumanTimeDiff diff;
-
-    EXPECT_TRUE(extract("42y23m-2d", &diff));
-    EXPECT_EQ(42, diff.year);
-    EXPECT_EQ(23, diff.month);
-    EXPECT_EQ(-2, diff.day);
-    EXPECT_EQ(0, diff.hour);
-    EXPECT_EQ(0, diff.minute);
-    EXPECT_EQ(0, diff.second);
-    EXPECT_FALSE(extract("1y2y", &diff));
-}
diff --git a/src/common/intern-pool.hpp b/src/common/intern-pool.hpp
deleted file mode 100644
index e75a359..0000000
--- a/src/common/intern-pool.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef INTERN_POOL_HPP
-#define INTERN_POOL_HPP
-
-# include <cassert>
-
-# include <map>
-# include <vector>
-
-# include "../strings/rstring.hpp"
-# include "../strings/zstring.hpp"
-# include "../strings/xstring.hpp"
-
-class InternPool
-{
-    std::map<RString, size_t> known;
-    std::vector<RString> names;
-public:
-    size_t intern(XString name_)
-    {
-        // TODO just look up the XString, the memory should not move by now
-        RString name = name_;
-        // hm, I could change this to do aliases
-        auto pair = known.insert({name, known.size()});
-        if (pair.second)
-            names.push_back(name);
-        assert (known.size() == names.size());
-        return pair.first->second;
-    }
-
-    ZString outtern(size_t sz) const
-    {
-        return names[sz];
-    }
-
-    size_t size() const
-    {
-        return known.size();
-    }
-};
-
-#endif //INTERN_POOL_HPP
diff --git a/src/common/intern-pool_test.cpp b/src/common/intern-pool_test.cpp
deleted file mode 100644
index bf17b67..0000000
--- a/src/common/intern-pool_test.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#include "intern-pool.hpp"
-
-#include <gtest/gtest.h>
-
-#include "../strings/base.hpp"
-
-TEST(InternPool, whydoesthisalwaysneedasecondname)
-{
-    InternPool p;
-    EXPECT_EQ(0, p.size());
-    EXPECT_EQ(0, p.intern("hello"));
-    EXPECT_EQ(0, p.intern("hello"));
-    EXPECT_EQ(1, p.size());
-    EXPECT_EQ(1, p.intern("world"));
-    EXPECT_EQ(0, p.intern("hello"));
-    EXPECT_EQ(1, p.intern("world"));
-    EXPECT_EQ(2, p.size());
-    EXPECT_EQ("hello", p.outtern(0));
-    EXPECT_EQ("world", p.outtern(1));
-}
diff --git a/src/common/ip.cpp b/src/common/ip.cpp
deleted file mode 100644
index 146734a..0000000
--- a/src/common/ip.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-#include "ip.hpp"
-//    ip.cpp - Implementation of IP address functions.
-//
-//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-#include "../strings/xstring.hpp"
-#include "../strings/vstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "../poison.hpp"
-
-bool extract(XString str, IP4Address *rv)
-{
-    if (str.endswith('.'))
-        return false;
-    uint8_t buf[4];
-    if (extract(str, record<'.'>(&buf[0], &buf[1], &buf[2], &buf[3])))
-    {
-        *rv = IP4Address(buf);
-        return true;
-    }
-    return false;
-}
-
-bool extract(XString str, IP4Mask *rv)
-{
-    IP4Address a, m;
-    unsigned b;
-    XString l, r;
-    if (str.endswith('/'))
-        return false;
-    if (extract(str, record<'/'>(&l, &r)))
-    {
-        // a.b.c.d/e.f.g.h or a.b.c.d/n
-        if (!extract(l, &a))
-            return false;
-        if (extract(r, &m))
-        {
-            *rv = IP4Mask(a, m);
-            return true;
-        }
-        if (!extract(r, &b) || b > 32)
-            return false;
-    }
-    else
-    {
-        // a.b.c.d or a.b.c.d. or a.b.c. or a.b. or a.
-        if (extract(str, &a))
-        {
-            *rv = IP4Mask(a, IP4_BROADCAST);
-            return true;
-        }
-        if (!str.endswith('.'))
-            return false;
-        uint8_t d[4] {};
-        if (extract(str, record<'.'>(&d[0], &d[1], &d[2], &d[3])))
-            b = 32;
-        else if (extract(str, record<'.'>(&d[0], &d[1], &d[2])))
-            b = 24;
-        else if (extract(str, record<'.'>(&d[0], &d[1])))
-            b = 16;
-        else if (extract(str, record<'.'>(&d[0])))
-            b = 8;
-        else
-            return false;
-        a = IP4Address(d);
-    }
-    // a is set; need to construct m from b
-    if (b == 0)
-        m = IP4Address();
-    else if (b == 32)
-        m = IP4_BROADCAST;
-    else
-    {
-        uint32_t s = -1;
-        s <<= (32 - b);
-        m = IP4Address({
-                static_cast<uint8_t>(s >> 24),
-                static_cast<uint8_t>(s >> 16),
-                static_cast<uint8_t>(s >> 8),
-                static_cast<uint8_t>(s >> 0),
-        });
-    }
-    *rv = IP4Mask(a, m);
-    return true;
-}
-
-VString<15> convert_for_printf(IP4Address a_)
-{
-    const uint8_t *a = a_.bytes();
-    return STRNPRINTF(16, "%hhu.%hhu.%hhu.%hhu", a[0], a[1], a[2], a[3]);
-}
-
-VString<31> convert_for_printf(IP4Mask a)
-{
-    return STRNPRINTF(32, "%s/%s",
-            a.addr(), a.mask());
-}
diff --git a/src/common/ip.hpp b/src/common/ip.hpp
deleted file mode 100644
index d6e6f04..0000000
--- a/src/common/ip.hpp
+++ /dev/null
@@ -1,164 +0,0 @@
-#ifndef TMWA_COMMON_IP_HPP
-#define TMWA_COMMON_IP_HPP
-//    ip.hpp - classes to deal with IP addresses.
-//
-//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include <netinet/in.h>
-
-# include "../strings/fwd.hpp"
-
-# include "extract.hpp"
-
-// TODO - in the long run ports belong here also
-// and of course, IPv6 stuff.
-// But what about unix socket addresses?
-
-/// Helper function
-template<class T, size_t n>
-constexpr
-bool _ce_a_lt(T (&a)[n], T (&b)[n], size_t i=0)
-{
-    return (i != n
-            && (a[i] < b[i]
-                || (a[i] == b[i]
-                    && _ce_a_lt(a, b, i + 1))));
-}
-
-/// A 32-bit Ipv4 address. Does not include a port.
-/// Guaranteed to be laid out like the network wants.
-class IP4Address
-{
-    uint8_t _addr[4];
-public:
-    constexpr
-    IP4Address()
-    : _addr{}
-    {}
-    constexpr explicit
-    IP4Address(const uint8_t (&a)[4])
-    : _addr{a[0], a[1], a[2], a[3]}
-    {}
-    explicit
-    IP4Address(struct in_addr addr)
-    {
-        static_assert(sizeof(addr) == sizeof(_addr), "4 bytes");
-        *this = IP4Address(reinterpret_cast<const uint8_t (&)[4]>(addr));
-    }
-    explicit
-    operator struct in_addr() const
-    {
-        return reinterpret_cast<const struct in_addr&>(_addr);
-    }
-
-    constexpr friend
-    IP4Address operator & (IP4Address l, IP4Address r)
-    {
-        return IP4Address({
-                static_cast<uint8_t>(l._addr[0] & r._addr[0]),
-                static_cast<uint8_t>(l._addr[1] & r._addr[1]),
-                static_cast<uint8_t>(l._addr[2] & r._addr[2]),
-                static_cast<uint8_t>(l._addr[3] & r._addr[3]),
-        });
-    }
-
-    IP4Address& operator &= (IP4Address m)
-    { return *this = *this & m; }
-
-    const uint8_t *bytes() const
-    { return _addr; }
-
-    constexpr friend
-    bool operator < (IP4Address l, IP4Address r)
-    {
-        return _ce_a_lt(l._addr, r._addr);
-    }
-
-    constexpr friend
-    bool operator > (IP4Address l, IP4Address r)
-    {
-        return _ce_a_lt(r._addr, l._addr);
-    }
-
-    constexpr friend
-    bool operator >= (IP4Address l, IP4Address r)
-    {
-        return !_ce_a_lt(l._addr, r._addr);
-    }
-
-    constexpr friend
-    bool operator <= (IP4Address l, IP4Address r)
-    {
-        return !_ce_a_lt(r._addr, l._addr);
-    }
-
-    constexpr friend
-    bool operator == (IP4Address l, IP4Address r)
-    {
-        return !(l < r || r < l);
-    }
-
-    constexpr friend
-    bool operator != (IP4Address l, IP4Address r)
-    {
-        return (l < r || r < l);
-    }
-};
-
-class IP4Mask
-{
-    IP4Address _addr, _mask;
-public:
-    constexpr
-    IP4Mask() : _addr(), _mask()
-    {}
-    constexpr
-    IP4Mask(IP4Address a, IP4Address m) : _addr(a & m), _mask(m)
-    {}
-
-    constexpr
-    IP4Address addr() const
-    { return _addr; }
-    constexpr
-    IP4Address mask() const
-    { return _mask; }
-
-    constexpr
-    bool covers(IP4Address a) const
-    {
-        return (a & _mask) == _addr;
-    }
-};
-
-
-constexpr
-IP4Address IP4_LOCALHOST({127, 0, 0, 1});
-constexpr
-IP4Address IP4_BROADCAST({255, 255, 255, 255});
-
-
-VString<15> convert_for_printf(IP4Address a);
-VString<31> convert_for_printf(IP4Mask m);
-
-bool extract(XString str, IP4Address *iv);
-
-bool extract(XString str, IP4Mask *iv);
-
-#endif // TMWA_COMMON_IP_HPP
diff --git a/src/common/ip.py b/src/common/ip.py
deleted file mode 100644
index e6a8183..0000000
--- a/src/common/ip.py
+++ /dev/null
@@ -1,14 +0,0 @@
-class IP4Address(object):
-    ''' print an IP4Address
-    '''
-    __slots__ = ('_value')
-    name = 'IP4Address'
-    enabled = True
-
-    def __init__(self, value):
-        self._value = value
-
-    def to_string(self):
-        addr = self._value['_addr']
-        addr = tuple(int(addr[i]) for i in range(4))
-        return '%d.%d.%d.%d' % addr
diff --git a/src/common/ip_test.cpp b/src/common/ip_test.cpp
deleted file mode 100644
index 7ef1047..0000000
--- a/src/common/ip_test.cpp
+++ /dev/null
@@ -1,332 +0,0 @@
-#include "ip.hpp"
-
-#include <gtest/gtest.h>
-
-#include "../io/cxxstdio.hpp"
-
-#define CB(X) (std::integral_constant<bool, (X)>::value)
-TEST(ip4addr, cmp)
-{
-    constexpr static
-    IP4Address a = IP4_LOCALHOST;
-    constexpr static
-    IP4Address b = IP4_BROADCAST;
-
-    EXPECT_FALSE(CB(a < a));
-    EXPECT_TRUE (CB(a < b));
-    EXPECT_FALSE(CB(b < a));
-    EXPECT_FALSE(CB(b < b));
-
-    EXPECT_FALSE(CB(a > a));
-    EXPECT_FALSE(CB(a > b));
-    EXPECT_TRUE (CB(b > a));
-    EXPECT_FALSE(CB(b > b));
-
-    EXPECT_TRUE (CB(a <= a));
-    EXPECT_TRUE (CB(a <= b));
-    EXPECT_FALSE(CB(b <= a));
-    EXPECT_TRUE (CB(b <= b));
-
-    EXPECT_TRUE (CB(a >= a));
-    EXPECT_FALSE(CB(a >= b));
-    EXPECT_TRUE (CB(b >= a));
-    EXPECT_TRUE (CB(b >= b));
-
-    EXPECT_TRUE (CB(a == a));
-    EXPECT_FALSE(CB(a == b));
-    EXPECT_FALSE(CB(b == a));
-    EXPECT_TRUE (CB(b == b));
-
-    EXPECT_FALSE(CB(a != a));
-    EXPECT_TRUE (CB(a != b));
-    EXPECT_TRUE (CB(b != a));
-    EXPECT_FALSE(CB(b != b));
-}
-
-TEST(ip4addr, str)
-{
-    IP4Address a;
-    EXPECT_EQ("0.0.0.0", STRNPRINTF(17, "%s", a));
-    EXPECT_EQ("127.0.0.1", STRNPRINTF(17, "%s", IP4_LOCALHOST));
-    EXPECT_EQ("255.255.255.255", STRNPRINTF(17, "%s", IP4_BROADCAST));
-}
-
-TEST(ip4addr, extract)
-{
-    IP4Address a;
-    EXPECT_TRUE(extract("0.0.0.0", &a));
-    EXPECT_EQ("0.0.0.0", STRNPRINTF(16, "%s", a));
-    EXPECT_TRUE(extract("127.0.0.1", &a));
-    EXPECT_EQ("127.0.0.1", STRNPRINTF(16, "%s", a));
-    EXPECT_TRUE(extract("255.255.255.255", &a));
-    EXPECT_EQ("255.255.255.255", STRNPRINTF(16, "%s", a));
-    EXPECT_TRUE(extract("1.2.3.4", &a));
-    EXPECT_EQ("1.2.3.4", STRNPRINTF(16, "%s", a));
-
-    EXPECT_FALSE(extract("1.2.3.4.5", &a));
-    EXPECT_FALSE(extract("1.2.3.4.", &a));
-    EXPECT_FALSE(extract("1.2.3.", &a));
-    EXPECT_FALSE(extract("1.2.3", &a));
-    EXPECT_FALSE(extract("1.2.", &a));
-    EXPECT_FALSE(extract("1.2", &a));
-    EXPECT_FALSE(extract("1.", &a));
-    EXPECT_FALSE(extract("1", &a));
-    EXPECT_FALSE(extract("", &a));
-}
-
-
-TEST(ip4mask, body)
-{
-    IP4Mask m;
-    EXPECT_EQ(IP4Address(), m.addr());
-    EXPECT_EQ(IP4Address(), m.mask());
-    m = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST);
-    EXPECT_EQ(IP4_LOCALHOST, m.addr());
-    EXPECT_EQ(IP4_BROADCAST, m.mask());
-}
-
-TEST(ip4mask, str)
-{
-    IP4Mask m;
-    EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(33, "%s", m));
-    m = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST);
-    EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(33, "%s", m));
-}
-
-TEST(ip4mask, extract)
-{
-    IP4Mask m;
-    EXPECT_FALSE(extract("9.8.7.6/33", &m));
-    EXPECT_FALSE(extract("9.8.7.6.5", &m));
-    EXPECT_FALSE(extract("9.8.7.6/", &m));
-    EXPECT_FALSE(extract("9.8.7", &m));
-    EXPECT_FALSE(extract("9.8", &m));
-    EXPECT_FALSE(extract("9", &m));
-
-    EXPECT_TRUE(extract("127.0.0.1", &m));
-    EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("127.0.0.1.", &m));
-    EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("127.0.0.", &m));
-    EXPECT_EQ("127.0.0.0/255.255.255.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("127.0.", &m));
-    EXPECT_EQ("127.0.0.0/255.255.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("127.", &m));
-    EXPECT_EQ("127.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m));
-
-    EXPECT_TRUE(extract("1.2.3.4/255.255.255.255", &m));
-    EXPECT_EQ("1.2.3.4/255.255.255.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.2.3.0/255.255.255.0", &m));
-    EXPECT_EQ("1.2.3.0/255.255.255.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.2.0.4/255.255.0.255", &m));
-    EXPECT_EQ("1.2.0.4/255.255.0.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.2.0.0/255.255.0.0", &m));
-    EXPECT_EQ("1.2.0.0/255.255.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.0.3.4/255.0.255.255", &m));
-    EXPECT_EQ("1.0.3.4/255.0.255.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.0.3.0/255.0.255.0", &m));
-    EXPECT_EQ("1.0.3.0/255.0.255.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.0.0.4/255.0.0.255", &m));
-    EXPECT_EQ("1.0.0.4/255.0.0.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("1.0.0.0/255.0.0.0", &m));
-    EXPECT_EQ("1.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.2.3.4/0.255.255.255", &m));
-    EXPECT_EQ("0.2.3.4/0.255.255.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.2.3.0/0.255.255.0", &m));
-    EXPECT_EQ("0.2.3.0/0.255.255.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.2.0.4/0.255.0.255", &m));
-    EXPECT_EQ("0.2.0.4/0.255.0.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.2.0.0/0.255.0.0", &m));
-    EXPECT_EQ("0.2.0.0/0.255.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.3.4/0.0.255.255", &m));
-    EXPECT_EQ("0.0.3.4/0.0.255.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.3.0/0.0.255.0", &m));
-    EXPECT_EQ("0.0.3.0/0.0.255.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.4/0.0.0.255", &m));
-    EXPECT_EQ("0.0.0.4/0.0.0.255", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/0.0.0.0", &m));
-    EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(32, "%s", m));
-
-    // please don't do this
-    EXPECT_TRUE(extract("120.248.200.217/89.57.126.5", &m));
-    EXPECT_EQ("88.56.72.1/89.57.126.5", STRNPRINTF(32, "%s", m));
-
-    EXPECT_TRUE(extract("0.0.0.0/32", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.255", STRNPRINTF(32, "%s", m));
-
-    EXPECT_TRUE(extract("0.0.0.0/31", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.254", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/30", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.252", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/29", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.248", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/28", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.240", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/27", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.224", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/26", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.192", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/25", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.128", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/24", &m));
-    EXPECT_EQ("0.0.0.0/255.255.255.0", STRNPRINTF(32, "%s", m));
-
-    EXPECT_TRUE(extract("0.0.0.0/23", &m));
-    EXPECT_EQ("0.0.0.0/255.255.254.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/22", &m));
-    EXPECT_EQ("0.0.0.0/255.255.252.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/21", &m));
-    EXPECT_EQ("0.0.0.0/255.255.248.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/20", &m));
-    EXPECT_EQ("0.0.0.0/255.255.240.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/19", &m));
-    EXPECT_EQ("0.0.0.0/255.255.224.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/18", &m));
-    EXPECT_EQ("0.0.0.0/255.255.192.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/17", &m));
-    EXPECT_EQ("0.0.0.0/255.255.128.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/16", &m));
-    EXPECT_EQ("0.0.0.0/255.255.0.0", STRNPRINTF(32, "%s", m));
-
-    EXPECT_TRUE(extract("0.0.0.0/15", &m));
-    EXPECT_EQ("0.0.0.0/255.254.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/14", &m));
-    EXPECT_EQ("0.0.0.0/255.252.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/13", &m));
-    EXPECT_EQ("0.0.0.0/255.248.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/12", &m));
-    EXPECT_EQ("0.0.0.0/255.240.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/11", &m));
-    EXPECT_EQ("0.0.0.0/255.224.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/10", &m));
-    EXPECT_EQ("0.0.0.0/255.192.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/9", &m));
-    EXPECT_EQ("0.0.0.0/255.128.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/8", &m));
-    EXPECT_EQ("0.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m));
-
-    EXPECT_TRUE(extract("0.0.0.0/7", &m));
-    EXPECT_EQ("0.0.0.0/254.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/6", &m));
-    EXPECT_EQ("0.0.0.0/252.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/5", &m));
-    EXPECT_EQ("0.0.0.0/248.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/4", &m));
-    EXPECT_EQ("0.0.0.0/240.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/3", &m));
-    EXPECT_EQ("0.0.0.0/224.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/2", &m));
-    EXPECT_EQ("0.0.0.0/192.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/1", &m));
-    EXPECT_EQ("0.0.0.0/128.0.0.0", STRNPRINTF(32, "%s", m));
-    EXPECT_TRUE(extract("0.0.0.0/0", &m));
-    EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(32, "%s", m));
-}
-
-TEST(ip4mask, cover)
-{
-    IP4Address a;
-    IP4Address b = IP4_BROADCAST;
-    IP4Address l = IP4_LOCALHOST;
-    IP4Address h({127, 255, 255, 255});
-    IP4Address p24l({10, 0, 0, 0});
-    IP4Address p24h({10, 255, 255, 255});
-    IP4Address p20l({172, 16, 0, 0});
-    IP4Address p20h({172, 31, 255, 255});
-    IP4Address p16l({192, 168, 0, 0});
-    IP4Address p16h({192, 168, 255, 255});
-    IP4Mask m;
-    EXPECT_TRUE(m.covers(a));
-    EXPECT_TRUE(m.covers(b));
-    EXPECT_TRUE(m.covers(l));
-    EXPECT_TRUE(m.covers(h));
-    EXPECT_TRUE(m.covers(p24l));
-    EXPECT_TRUE(m.covers(p24h));
-    EXPECT_TRUE(m.covers(p20l));
-    EXPECT_TRUE(m.covers(p20h));
-    EXPECT_TRUE(m.covers(p16l));
-    EXPECT_TRUE(m.covers(p16h));
-    m = IP4Mask(l, a);
-    EXPECT_TRUE(m.covers(a));
-    EXPECT_TRUE(m.covers(b));
-    EXPECT_TRUE(m.covers(l));
-    EXPECT_TRUE(m.covers(h));
-    EXPECT_TRUE(m.covers(p24l));
-    EXPECT_TRUE(m.covers(p24h));
-    EXPECT_TRUE(m.covers(p20l));
-    EXPECT_TRUE(m.covers(p20h));
-    EXPECT_TRUE(m.covers(p16l));
-    EXPECT_TRUE(m.covers(p16h));
-    m = IP4Mask(l, b);
-    EXPECT_FALSE(m.covers(a));
-    EXPECT_FALSE(m.covers(b));
-    EXPECT_TRUE(m.covers(l));
-    EXPECT_FALSE(m.covers(h));
-    EXPECT_FALSE(m.covers(p24l));
-    EXPECT_FALSE(m.covers(p24h));
-    EXPECT_FALSE(m.covers(p20l));
-    EXPECT_FALSE(m.covers(p20h));
-    EXPECT_FALSE(m.covers(p16l));
-    EXPECT_FALSE(m.covers(p16h));
-
-    // but the really useful ones are with partial masks
-    m = IP4Mask(IP4Address({10, 0, 0, 0}), IP4Address({255, 0, 0, 0}));
-    EXPECT_FALSE(m.covers(a));
-    EXPECT_FALSE(m.covers(b));
-    EXPECT_FALSE(m.covers(l));
-    EXPECT_FALSE(m.covers(h));
-    EXPECT_TRUE(m.covers(p24l));
-    EXPECT_TRUE(m.covers(p24h));
-    EXPECT_FALSE(m.covers(p20l));
-    EXPECT_FALSE(m.covers(p20h));
-    EXPECT_FALSE(m.covers(p16l));
-    EXPECT_FALSE(m.covers(p16h));
-    EXPECT_FALSE(m.covers(IP4Address({9, 255, 255, 255})));
-    EXPECT_FALSE(m.covers(IP4Address({11, 0, 0, 0})));
-    m = IP4Mask(IP4Address({127, 0, 0, 0}), IP4Address({255, 0, 0, 0}));
-    EXPECT_FALSE(m.covers(a));
-    EXPECT_FALSE(m.covers(b));
-    EXPECT_TRUE(m.covers(l));
-    EXPECT_TRUE(m.covers(h));
-    EXPECT_FALSE(m.covers(p24l));
-    EXPECT_FALSE(m.covers(p24h));
-    EXPECT_FALSE(m.covers(p20l));
-    EXPECT_FALSE(m.covers(p20h));
-    EXPECT_FALSE(m.covers(p16l));
-    EXPECT_FALSE(m.covers(p16h));
-    EXPECT_FALSE(m.covers(IP4Address({126, 255, 255, 255})));
-    EXPECT_FALSE(m.covers(IP4Address({128, 0, 0, 0})));
-    m = IP4Mask(IP4Address({172, 16, 0, 0}), IP4Address({255, 240, 0, 0}));
-    EXPECT_FALSE(m.covers(a));
-    EXPECT_FALSE(m.covers(b));
-    EXPECT_FALSE(m.covers(l));
-    EXPECT_FALSE(m.covers(h));
-    EXPECT_FALSE(m.covers(p24l));
-    EXPECT_FALSE(m.covers(p24h));
-    EXPECT_TRUE(m.covers(p20l));
-    EXPECT_TRUE(m.covers(p20h));
-    EXPECT_FALSE(m.covers(p16l));
-    EXPECT_FALSE(m.covers(p16h));
-    EXPECT_FALSE(m.covers(IP4Address({172, 15, 255, 255})));
-    EXPECT_FALSE(m.covers(IP4Address({172, 32, 0, 0})));
-    m = IP4Mask(IP4Address({192, 168, 0, 0}), IP4Address({255, 255, 0, 0}));
-    EXPECT_FALSE(m.covers(a));
-    EXPECT_FALSE(m.covers(b));
-    EXPECT_FALSE(m.covers(l));
-    EXPECT_FALSE(m.covers(h));
-    EXPECT_FALSE(m.covers(p24l));
-    EXPECT_FALSE(m.covers(p24h));
-    EXPECT_FALSE(m.covers(p20l));
-    EXPECT_FALSE(m.covers(p20h));
-    EXPECT_TRUE(m.covers(p16l));
-    EXPECT_TRUE(m.covers(p16h));
-    EXPECT_FALSE(m.covers(IP4Address({192, 167, 255, 255})));
-    EXPECT_FALSE(m.covers(IP4Address({192, 169, 0, 0})));
-
-    // OTOH this is crazy
-    EXPECT_TRUE(extract("120.248.200.217/89.57.126.5", &m));
-    EXPECT_TRUE(m.covers(IP4Address({120, 248, 200, 217})));
-    EXPECT_TRUE(m.covers(IP4Address({88, 56, 72, 1})));
-    EXPECT_FALSE(m.covers(IP4Address({88, 56, 72, 0})));
-    EXPECT_FALSE(m.covers(IP4Address({88, 56, 72, 255})));
-}
diff --git a/src/common/iter.hpp b/src/common/iter.hpp
deleted file mode 100644
index 5b39588..0000000
--- a/src/common/iter.hpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#ifndef TMWA_COMMON_ITER_HPP
-#define TMWA_COMMON_ITER_HPP
-//    iter.hpp - tools for dealing with iterators
-//
-//    Copyright © 2012-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    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/>.
-
-# include "../sanity.hpp"
-
-# include <iterator>
-
-
-/// Simple class to use a pair of iterators with foreach
-template<class It>
-class IteratorPair
-{
-    It _b, _e;
-public:
-    IteratorPair(It b, It e)
-    : _b(b), _e(e)
-    {}
-
-    It begin() { return _b; }
-    It end() { return _e; }
-};
-
-template<class It>
-IteratorPair<It> iterator_pair(It b, It e)
-{
-    return {b, e};
-}
-
-template<class T>
-class PassthroughMath
-{
-public:
-    static
-    T inced(T v) { return ++v; }
-};
-
-// An iterator that just directly contains an integer-like value
-// TODO port this once the new integer API happens
-template<class T, class Math=PassthroughMath<T>>
-class ValueIterator
-{
-    T value;
-public:
-    typedef std::forward_iterator_tag iterator_category;
-    typedef void difference_type;
-    typedef T value_type;
-    typedef void reference;
-    typedef void pointer;
-public:
-    ValueIterator(T v)
-    : value(v)
-    {}
-
-    T operator *()
-    {
-        return value;
-    }
-    ValueIterator& operator++ ()
-    {
-        value = Math::inced(value);
-        return *this;
-    }
-    friend bool operator == (ValueIterator l, ValueIterator r)
-    {
-        return l.value == r.value;
-    }
-    friend bool operator != (ValueIterator l, ValueIterator r)
-    {
-        return !(l == r);
-    }
-};
-
-template<class T>
-IteratorPair<ValueIterator<T>> value_range(T b, T e)
-{
-    return {b, e};
-}
-
-#endif // TMWA_COMMON_ITER_HPP
diff --git a/src/common/iter_test.cpp b/src/common/iter_test.cpp
deleted file mode 100644
index 647ebf9..0000000
--- a/src/common/iter_test.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-#include "iter.hpp"
-
-#include <gtest/gtest.h>
-
-#include "../strings/xstring.hpp"
-
-TEST(iterpair, string)
-{
-    IteratorPair<ValueIterator<char>> pair = value_range('0', ':');
-    const char *str = "0123456789";
-    EXPECT_TRUE(std::equal(pair.begin(), pair.end(), str));
-}
-
-TEST(iterpair, signed8)
-{
-    IteratorPair<ValueIterator<int8_t>> pair = value_range(int8_t(-128), int8_t(127));
-    int8_t arr[255] =
-    {
-        -128, -127, -126, -125, -124, -123, -122, -121, -120,
-        -119, -118, -117, -116, -115, -114, -113, -112, -111, -110,
-        -109, -108, -107, -106, -105, -104, -103, -102, -101, -100,
-        -99, -98, -97, -96, -95, -94, -93, -92, -91, -90,
-        -89, -88, -87, -86, -85, -84, -83, -82, -81, -80,
-        -79, -78, -77, -76, -75, -74, -73, -72, -71, -70,
-        -69, -68, -67, -66, -65, -64, -63, -62, -61, -60,
-        -59, -58, -57, -56, -55, -54, -53, -52, -51, -50,
-        -49, -48, -47, -46, -45, -44, -43, -42, -41, -40,
-        -39, -38, -37, -36, -35, -34, -33, -32, -31, -30,
-        -29, -28, -27, -26, -25, -24, -23, -22, -21, -20,
-        -19, -18, -17, -16, -15, -14, -13, -12, -11, -10,
-        -9, -8, -7, -6, -5, -4, -3, -2, -1,
-        0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-        10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
-        20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
-        30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
-        40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
-        50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
-        60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
-        70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
-        80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
-        90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
-        100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
-        110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
-        120, 121, 122, 123, 124, 125, 126,
-    };
-    EXPECT_TRUE(std::equal(pair.begin(), pair.end(), arr + 0));
-}
-
-TEST(iterpair, unsigned8)
-{
-    IteratorPair<ValueIterator<uint8_t>> pair = value_range(uint8_t(0), uint8_t(255));
-    uint8_t arr[255] =
-    {
-        0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-        10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
-        20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
-        30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
-        40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
-        50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
-        60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
-        70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
-        80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
-        90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
-        100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
-        110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
-        120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
-        130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
-        140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
-        150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
-        160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
-        170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
-        180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
-        190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
-        200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
-        210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
-        220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
-        230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
-        240, 241, 242, 243, 244, 245, 246, 247, 248, 249,
-        250, 251, 252, 253, 254,
-    };
-    EXPECT_TRUE(std::equal(pair.begin(), pair.end(), arr));
-}
diff --git a/src/common/matrix.hpp b/src/common/matrix.hpp
deleted file mode 100644
index 8595191..0000000
--- a/src/common/matrix.hpp
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef TMWA_COMMON_MATRIX_HPP
-#define TMWA_COMMON_MATRIX_HPP
-
-template<class T>
-class Matrix
-{
-    std::unique_ptr<T[]> _data;
-    size_t _xs, _ys;
-public:
-    Matrix()
-    : _data()
-    , _xs()
-    , _ys()
-    {}
-    Matrix(size_t x, size_t y)
-    : _data(new T[x * y]())
-    , _xs(x)
-    , _ys(y)
-    {}
-    // no copy-ctor or copy-assign
-
-    void reset(size_t x, size_t y)
-    {
-        *this = Matrix(x, y);
-    }
-    void clear()
-    {
-        *this = Matrix();
-    }
-
-    T& ref(size_t x, size_t y)
-    {
-        return _data[x + y * _xs];
-    }
-    const T& ref(size_t x, size_t y) const
-    {
-        return _data[x + y * _xs];
-    }
-
-    size_t xs() const
-    {
-        return _xs;
-    }
-    size_t ys() const
-    {
-        return _ys;
-    }
-};
-
-#endif // TMWA_COMMON_MATRIX_HPP
diff --git a/src/common/md5calc.cpp b/src/common/md5calc.cpp
deleted file mode 100644
index d23f8e3..0000000
--- a/src/common/md5calc.cpp
+++ /dev/null
@@ -1,352 +0,0 @@
-#include "md5calc.hpp"
-
-#include <cstring>
-
-#include "../strings/xstring.hpp"
-#include "../strings/vstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-#include "../io/read.hpp"
-
-#include "random.hpp"
-#include "utils.hpp"
-
-#include "../poison.hpp"
-
-// auxilary data
-/*
-sin() constant table
-#Reformatted output of:
-echo 'scale=40; obase=16; for (i=1;i<=64;i++) print 2^32 * sin(i), "\n"' |
-bc | sed 's/^-//;s/^/0x/;s/\..*$/,/'
-*/
-static
-const uint32_t T[64] =
-{
-    // used by round 1
-    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0
-    0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4
-    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8
-    0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12
-    // used by round 2
-    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16
-    0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, //20
-    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24
-    0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28
-    // used by round 3
-    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32
-    0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36
-    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, //40
-    0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44
-    // used by round 4
-    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48
-    0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52
-    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56
-    0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, //60
-};
-
-// auxilary functions
-// note - the RFC defines these by non-CS conventions: or=v, and=(empty)
-static
-uint32_t rotate_left(uint32_t val, unsigned shift)
-{
-    return val << shift | val >> (32 - shift);
-}
-
-static
-uint32_t F(uint32_t X, uint32_t Y, uint32_t Z)
-{
-    return (X & Y) | (~X & Z);
-}
-static
-uint32_t G(uint32_t X, uint32_t Y, uint32_t Z)
-{
-    return (X & Z) | (Y & ~Z);
-}
-static
-uint32_t H(uint32_t X, uint32_t Y, uint32_t Z)
-{
-    return X ^ Y ^ Z;
-}
-static
-uint32_t I(uint32_t X, uint32_t Y, uint32_t Z)
-{
-    return Y ^ (X | ~Z);
-}
-
-static
-const struct
-{
-    uint8_t k : 4;
-    uint8_t : 0;
-    uint8_t s : 5;
-//    uint8_t i : 6; just increments constantly, from 1 .. 64 over all rounds
-}
-MD5_round1[16] =
-{
-    { 0,  7}, { 1, 12}, { 2, 17}, { 3, 22},
-    { 4,  7}, { 5, 12}, { 6, 17}, { 7, 22},
-    { 8,  7}, { 9, 12}, {10, 17}, {11, 22},
-    {12,  7}, {13, 12}, {14, 17}, {15, 22},
-},
-MD5_round2[16] =
-{
-    { 1,  5}, { 6,  9}, {11, 14}, { 0, 20},
-    { 5,  5}, {10,  9}, {15, 14}, { 4, 20},
-    { 9,  5}, {14,  9}, { 3, 14}, { 8, 20},
-    {13,  5}, { 2,  9}, { 7, 14}, {12, 20},
-},
-MD5_round3[16] =
-{
-    { 5,  4}, { 8, 11}, {11, 16}, {14, 23},
-    { 1,  4}, { 4, 11}, { 7, 16}, {10, 23},
-    {13,  4}, { 0, 11}, { 3, 16}, { 6, 23},
-    { 9,  4}, {12, 11}, {15, 16}, { 2, 23},
-},
-MD5_round4[16] =
-{
-    { 0,  6}, { 7, 10}, {14, 15}, { 5, 21},
-    {12,  6}, { 3, 10}, {10, 15}, { 1, 21},
-    { 8,  6}, {15, 10}, { 6, 15}, {13, 21},
-    { 4,  6}, {11, 10}, { 2, 15}, { 9, 21},
-};
-
-
-void MD5_init(MD5_state* state)
-{
-    // in the RFC, these are specified as bytes, interpreted as little-endian
-    state->val[0] = 0x67452301;
-    state->val[1] = 0xEFCDAB89;
-    state->val[2] = 0x98BADCFE;
-    state->val[3] = 0x10325476;
-}
-
-void MD5_do_block(MD5_state* state, MD5_block block)
-{
-#define X block.data
-#define a state->val[(16 - i) % 4]
-#define b state->val[(17 - i) % 4]
-#define c state->val[(18 - i) % 4]
-#define d state->val[(19 - i) % 4]
-    // save the values
-    const MD5_state saved = *state;
-    // round 1
-    for (int i = 0; i < 16; i++)
-    {
-#define k MD5_round1[i].k
-#define s MD5_round1[i].s
-        a = b + rotate_left(a + F(b, c, d) + X[k] + T[i + 0x0], s);
-#undef k
-#undef s
-    }
-    // round 2
-    for (int i = 0; i < 16; i++)
-    {
-#define k MD5_round2[i].k
-#define s MD5_round2[i].s
-        a = b + rotate_left(a + G(b, c, d) + X[k] + T[i + 0x10], s);
-#undef k
-#undef s
-    }
-    // round 3
-    for (int i = 0; i < 16; i++)
-    {
-#define k MD5_round3[i].k
-#define s MD5_round3[i].s
-        a = b + rotate_left(a + H(b, c, d) + X[k] + T[i + 0x20], s);
-#undef k
-#undef s
-    }
-    // round 4
-    for (int i = 0; i < 16; i++)
-    {
-#define k MD5_round4[i].k
-#define s MD5_round4[i].s
-        a = b + rotate_left(a + I(b, c, d) + X[k] + T[i + 0x30], s);
-#undef k
-#undef s
-    }
-    // adjust state based on original
-    state->val[0] += saved.val[0];
-    state->val[1] += saved.val[1];
-    state->val[2] += saved.val[2];
-    state->val[3] += saved.val[3];
-#undef a
-#undef b
-#undef c
-#undef d
-}
-
-void MD5_to_bin(MD5_state state, md5_binary& out)
-{
-    for (int i = 0; i < 0x10; i++)
-        out[i] = state.val[i / 4] >> 8 * (i % 4);
-}
-
-static
-const char hex[] = "0123456789abcdef";
-
-void MD5_to_str(MD5_state state, md5_string& out_)
-{
-    md5_binary bin;
-    MD5_to_bin(state, bin);
-    char out[0x20];
-    for (int i = 0; i < 0x10; i++)
-        out[2 * i] = hex[bin[i] >> 4],
-        out[2 * i + 1] = hex[bin[i] & 0xf];
-    out_ = stringish<md5_string>(XString(out, out + 0x20, nullptr));
-}
-
-MD5_state MD5_from_string(XString msg)
-{
-    MD5_state state;
-    MD5_init(&state);
-    MD5_block block;
-    const uint64_t msg_full_len = msg.size();
-    while (msg.size() >= 64)
-    {
-        for (int i = 0; i < 0x10; i++)
-            X[i] = msg[4 * i + 0] | msg[4 * i + 1] << 8 | msg[4 * i + 2] << 16 | msg[4 * i + 3] << 24;
-        MD5_do_block(&state, block);
-        msg = msg.xslice_t(64);
-    }
-    // now pad 1-512 bits + the 64-bit length - may be two blocks
-    uint8_t buf[0x40] = {};
-    really_memcpy(buf, reinterpret_cast<const uint8_t *>(msg.data()), msg.size());
-    buf[msg.size()] = 0x80; // a single one bit
-    if (64 - msg.size() > 8)
-    {
-        for (int i = 0; i < 8; i++)
-            buf[0x38 + i] = (msg_full_len * 8) >> (i * 8);
-    }
-    for (int i = 0; i < 0x10; i++)
-        X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
-    MD5_do_block(&state, block);
-    if (64 - msg.size() <= 8)
-    {
-        really_memset0(buf, 0x38);
-        for (int i = 0; i < 8; i++)
-            buf[0x38 + i] = (msg_full_len * 8) >> (i * 8);
-        for (int i = 0; i < 0x10; i++)
-            X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
-        MD5_do_block(&state, block);
-    }
-    return state;
-}
-
-// TODO - refactor MD5 into a stream, and merge the implementations
-// I once implemented an ostream that does it ...
-MD5_state MD5_from_FILE(io::ReadFile& in)
-{
-    uint64_t total_len = 0;
-
-    uint8_t buf[0x40];
-    uint8_t block_len = 0;
-
-    MD5_state state;
-    MD5_init(&state);
-
-    MD5_block block;
-
-    while (true)
-    {
-        size_t rv = in.get(sign_cast<char *>(buf + block_len), 0x40 - block_len);
-        if (!rv)
-            break;
-        total_len += 8 * rv; // in bits
-        block_len += rv;
-        if (block_len != 0x40)
-            continue;
-        for (int i = 0; i < 0x10; i++)
-            X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
-        MD5_do_block(&state, block);
-        block_len = 0;
-    }
-    // no more input, just pad and append the length
-    buf[block_len] = 0x80;
-    really_memset0(buf + block_len + 1, 0x40 - block_len - 1);
-    if (block_len < 0x38)
-    {
-        for (int i = 0; i < 8; i++)
-            buf[0x38 + i] = total_len >> i * 8;
-    }
-    for (int i = 0; i < 0x10; i++)
-        X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
-    MD5_do_block(&state, block);
-    if (0x38 <= block_len)
-    {
-        really_memset0(buf, 0x38);
-        for (int i = 0; i < 8; i++)
-            buf[0x38 + i] = total_len >> i * 8;
-        for (int i = 0; i < 0x10; i++)
-            X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
-        MD5_do_block(&state, block);
-    }
-    return state;
-}
-
-
-// Hash a password with a salt.
-// Whoever wrote this FAILS programming
-AccountCrypt MD5_saltcrypt(AccountPass key, SaltString salt)
-{
-    char cbuf[64] {};
-
-    // hash the key then the salt
-    // buf ends up as a 64-char NUL-terminated string
-    md5_string tbuf, tbuf2;
-    MD5_to_str(MD5_from_string(key), tbuf);
-    MD5_to_str(MD5_from_string(salt), tbuf2);
-    const auto it = std::copy(tbuf.begin(), tbuf.end(), std::begin(cbuf));
-    auto it2 = std::copy(tbuf2.begin(), tbuf2.end(), it);
-    assert(it2 == std::end(cbuf));
-
-    md5_string tbuf3;
-    MD5_to_str(MD5_from_string(XString(std::begin(cbuf), it2, nullptr)), tbuf3);
-
-    VString<31> obuf;
-
-    // This truncates the string, but we have to keep it like that for compatibility
-    SNPRINTF(obuf, 32, "!%s$%s", salt, tbuf3);
-    return stringish<AccountCrypt>(obuf);
-}
-
-SaltString make_salt(void)
-{
-    char salt[5];
-    for (int i = 0; i < 5; i++)
-        // 126 would probably actually be okay
-        salt[i] = random_::in(48, 125);
-    return stringish<SaltString>(XString(salt + 0, salt + 5, nullptr));
-}
-
-bool pass_ok(AccountPass password, AccountCrypt crypted)
-{
-    // crypted is like !salt$hash
-    auto begin = crypted.begin() + 1;
-    auto end = std::find(begin, crypted.end(), '$');
-    SaltString salt = stringish<SaltString>(crypted.xislice(begin, end));
-
-    return crypted == MD5_saltcrypt(password, salt);
-}
-
-// [M|h]ashes up an IP address and a secret key
-// to return a hopefully unique masked IP.
-IP4Address MD5_ip(IP4Address ip)
-{
-    static SaltString secret = make_salt();
-
-    // MD5sum a secret + the IP address
-    VString<31> ipbuf;
-    SNPRINTF(ipbuf, 32, "%s %s", ip, secret);
-    md5_binary obuf;
-    MD5_to_bin(MD5_from_string(ipbuf), obuf);
-
-    // Fold the md5sum to 32 bits, pack the bytes to an in_addr
-    return IP4Address({
-            static_cast<uint8_t>(obuf[0] ^ obuf[1] ^ obuf[8] ^ obuf[9]),
-            static_cast<uint8_t>(obuf[2] ^ obuf[3] ^ obuf[10] ^ obuf[11]),
-            static_cast<uint8_t>(obuf[4] ^ obuf[5] ^ obuf[12] ^ obuf[13]),
-            static_cast<uint8_t>(obuf[6] ^ obuf[7] ^ obuf[14] ^ obuf[15]),
-    });
-}
diff --git a/src/common/md5calc.hpp b/src/common/md5calc.hpp
deleted file mode 100644
index 45bec84..0000000
--- a/src/common/md5calc.hpp
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef MD5CALC_HPP
-#define MD5CALC_HPP
-
-# include "../sanity.hpp"
-
-# include <netinet/in.h>
-
-# include <cstdint>
-# include <cstddef>
-# include <cstdio>
-
-# include <array>
-
-# include "../strings/fwd.hpp"
-# include "../strings/vstring.hpp"
-
-# include "../io/fwd.hpp"
-
-# include "ip.hpp"
-# include "mmo.hpp"
-
-/// The digest state - becomes the output
-struct MD5_state
-{
-    // classically named {A,B,C,D}
-    // but use an so we can index
-    uint32_t val[4];
-};
-struct MD5_block
-{
-    uint32_t data[16];
-};
-
-struct md5_binary : std::array<uint8_t, 0x10> {};
-struct md5_string : VString<0x20> {};
-struct SaltString : VString<5> {};
-
-// Implementation
-void MD5_init(MD5_state *state);
-void MD5_do_block(MD5_state *state, MD5_block block);
-
-// Output formatting
-void MD5_to_bin(MD5_state state, md5_binary& out);
-void MD5_to_str(MD5_state state, md5_string& out);
-
-// Convenience
-MD5_state MD5_from_string(XString msg);
-MD5_state MD5_from_FILE(io::ReadFile& in);
-
-
-// whoever wrote this fails basic understanding of
-AccountCrypt MD5_saltcrypt(AccountPass key, SaltString salt);
-
-/// return some random characters (statically allocated)
-// Currently, returns a 5-char string
-SaltString make_salt(void);
-
-/// check plaintext password against saved saltcrypt
-bool pass_ok(AccountPass password, AccountCrypt crypted);
-
-/// This returns an IP4Address because it is configurable whether it gets called at all
-IP4Address MD5_ip(IP4Address ip);
-
-#endif // MD5CALC_HPP
diff --git a/src/common/md5calc_test.cpp b/src/common/md5calc_test.cpp
deleted file mode 100644
index ab5f242..0000000
--- a/src/common/md5calc_test.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "md5calc.hpp"
-
-#include <gtest/gtest.h>
-
-#include "../strings/xstring.hpp"
-#include "../strings/vstring.hpp"
-
-#include "utils.hpp"
-
-// This should be made part of the main API,
-// but is not yet to keep the diff small.
-// Edit: hack to fix the new strict comparison.
-static
-VString<32> MD5(XString in)
-{
-    md5_string out;
-    MD5_to_str(MD5_from_string(in), out);
-    return out;
-}
-
-TEST(md5calc, rfc1321)
-{
-    EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", MD5(""));
-    EXPECT_EQ("0cc175b9c0f1b6a831c399e269772661", MD5("a"));
-    EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", MD5("abc"));
-    EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", MD5("message digest"));
-    EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", MD5("abcdefghijklmnopqrstuvwxyz"));
-    EXPECT_EQ("d174ab98d277d9f5a5611c2c9f419d9f", MD5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"));
-    EXPECT_EQ("57edf4a22be3c955ac49da2e2107b67a", MD5("12345678901234567890123456789012345678901234567890123456789012345678901234567890"));
-}
diff --git a/src/common/mmo.hpp b/src/common/mmo.hpp
deleted file mode 100644
index bb30682..0000000
--- a/src/common/mmo.hpp
+++ /dev/null
@@ -1,380 +0,0 @@
-/// Global structures and defines
-#ifndef MMO_HPP
-#define MMO_HPP
-
-# include "../sanity.hpp"
-
-# include "../strings/vstring.hpp"
-
-# include "timer.t.hpp"
-# include "utils2.hpp"
-
-// affects CharName
-# define NAME_IGNORING_CASE 1
-
-constexpr int FIFOSIZE_SERVERLINK = 256 * 1024;
-
-constexpr int MAX_MAP_PER_SERVER = 512;
-constexpr int MAX_INVENTORY = 100;
-constexpr int MAX_AMOUNT = 30000;
-constexpr int MAX_ZENY = 1000000000;     // 1G zeny
-
-enum class SkillID : uint16_t;
-constexpr SkillID MAX_SKILL = SkillID(474); // not 450
-constexpr SkillID get_enum_min_value(SkillID) { return SkillID(); }
-constexpr SkillID get_enum_max_value(SkillID) { return MAX_SKILL; }
-
-constexpr int GLOBAL_REG_NUM = 96;
-constexpr int ACCOUNT_REG_NUM = 16;
-constexpr int ACCOUNT_REG2_NUM = 16;
-constexpr interval_t DEFAULT_WALK_SPEED = std::chrono::milliseconds(150);
-constexpr interval_t MIN_WALK_SPEED = interval_t::zero();
-constexpr interval_t MAX_WALK_SPEED = std::chrono::seconds(1);
-constexpr int MAX_STORAGE = 300;
-constexpr int MAX_PARTY = 12;
-
-# define MIN_HAIR_STYLE battle_config.min_hair_style
-# define MAX_HAIR_STYLE battle_config.max_hair_style
-# define MIN_HAIR_COLOR battle_config.min_hair_color
-# define MAX_HAIR_COLOR battle_config.max_hair_color
-# define MIN_CLOTH_COLOR battle_config.min_cloth_color
-# define MAX_CLOTH_COLOR battle_config.max_cloth_color
-
-struct AccountName : VString<23> {};
-struct AccountPass : VString<23> {};
-struct AccountCrypt : VString<39> {};
-struct AccountEmail : VString<39> {};
-struct ServerName : VString<19> {};
-struct PartyName : VString<23> {};
-struct VarName : VString<31> {};
-template<class T>
-T stringish(VString<sizeof(T) - 1> iv)
-{
-    T rv;
-    static_cast<VString<sizeof(T) - 1>&>(rv) = iv;
-    return rv;
-}
-# define DEFAULT_EMAIL stringish<AccountEmail>("a@a.com")
-
-// It is decreed: a mapname shall not contain an extension
-class MapName : public strings::_crtp_string<MapName, MapName, strings::ZPair>
-{
-    VString<15> _impl;
-public:
-    MapName() = default;
-    MapName(VString<15> v) : _impl(v.xislice_h(std::find(v.begin(), v.end(), '.'))) {}
-
-    iterator begin() const { return &*_impl.begin(); }
-    iterator end() const { return &*_impl.end(); }
-    const char *c_str() const { return _impl.c_str(); }
-
-    operator RString() const { return _impl; }
-    operator AString() const { return _impl; }
-    operator TString() const { return _impl; }
-    operator SString() const { return _impl; }
-    operator ZString() const { return _impl; }
-    operator XString() const { return _impl; }
-};
-template<>
-inline
-MapName stringish<MapName>(VString<15> iv)
-{
-    return iv;
-}
-inline
-const char *decay_for_printf(const MapName& vs) { return vs.c_str(); }
-
-// It is decreed: a charname is sometimes case sensitive
-struct CharName
-{
-private:
-    VString<23> _impl;
-public:
-    CharName() = default;
-    explicit CharName(VString<23> name)
-    : _impl(name)
-    {}
-
-    VString<23> to__actual() const
-    {
-        return _impl;
-    }
-    VString<23> to__lower() const
-    {
-        return _impl.to_lower();
-    }
-    VString<23> to__upper() const
-    {
-        return _impl.to_upper();
-    }
-    VString<23> to__canonical() const
-    {
-# if NAME_IGNORING_CASE == 0
-        return to__actual();
-# endif
-# if NAME_IGNORING_CASE == 1
-        return to__lower();
-# endif
-    }
-
-    friend bool operator == (const CharName& l, const CharName& r)
-    { return l.to__canonical() == r.to__canonical(); }
-    friend bool operator != (const CharName& l, const CharName& r)
-    { return l.to__canonical() != r.to__canonical(); }
-    friend bool operator < (const CharName& l, const CharName& r)
-    { return l.to__canonical() < r.to__canonical(); }
-    friend bool operator <= (const CharName& l, const CharName& r)
-    { return l.to__canonical() <= r.to__canonical(); }
-    friend bool operator > (const CharName& l, const CharName& r)
-    { return l.to__canonical() > r.to__canonical(); }
-    friend bool operator >= (const CharName& l, const CharName& r)
-    { return l.to__canonical() >= r.to__canonical(); }
-
-    friend
-    VString<23> convert_for_printf(const CharName& vs) { return vs.to__actual(); }
-};
-template<>
-inline
-CharName stringish<CharName>(VString<23> iv)
-{
-    return CharName(iv);
-}
-
-namespace e
-{
-enum class EPOS : uint16_t
-{
-    ZERO    = 0x0000,
-
-    LEGS    = 0x0001,
-    WEAPON  = 0x0002,
-    GLOVES  = 0x0004,
-    CAPE    = 0x0008,
-    MISC1   = 0x0010,
-    SHIELD  = 0x0020,
-    SHOES   = 0x0040,
-    MISC2   = 0x0080,
-    HAT     = 0x0100,
-    TORSO   = 0x0200,
-
-    ARROW   = 0x8000,
-};
-ENUM_BITWISE_OPERATORS(EPOS)
-
-constexpr EPOS get_enum_min_value(EPOS) { return EPOS(0x0000); }
-constexpr EPOS get_enum_max_value(EPOS) { return EPOS(0xffff); }
-}
-using e::EPOS;
-
-struct item
-{
-    int id;
-    short nameid;
-    short amount;
-    EPOS equip;
-};
-
-struct point
-{
-    MapName map_;
-    short x, y;
-};
-
-namespace e
-{
-enum class SkillFlags : uint16_t;
-}
-using e::SkillFlags;
-
-struct skill_value
-{
-    unsigned short lv;
-    SkillFlags flags;
-
-    friend bool operator == (const skill_value& l, const skill_value& r)
-    {
-        return l.lv == r.lv && l.flags == r.flags;
-    }
-    friend bool operator != (const skill_value& l, const skill_value& r)
-    {
-        return !(l == r);
-    }
-};
-
-struct global_reg
-{
-    VarName str;
-    int value;
-};
-
-// Option and Opt1..3 in map.hpp
-namespace e
-{
-enum class Option : uint16_t;
-constexpr Option get_enum_min_value(Option) { return Option(0x0000); }
-constexpr Option get_enum_max_value(Option) { return Option(0xffff); }
-}
-using e::Option;
-
-enum class ATTR
-{
-    STR = 0,
-    AGI = 1,
-    VIT = 2,
-    INT = 3,
-    DEX = 4,
-    LUK = 5,
-
-    COUNT = 6,
-};
-
-constexpr ATTR ATTRs[6] =
-{
-    ATTR::STR,
-    ATTR::AGI,
-    ATTR::VIT,
-    ATTR::INT,
-    ATTR::DEX,
-    ATTR::LUK,
-};
-
-enum class ItemLook : uint16_t
-{
-    NONE = 0,
-    BLADE = 1, // or some other common weapons
-    _2,
-    SETZER_AND_SCYTHE = 3,
-    _6,
-    STAFF = 10,
-    BOW = 11,
-    _13 = 13,
-    _14 = 14,
-    _16 = 16,
-    SINGLE_HANDED_COUNT = 17,
-
-    DUAL_BLADE = 0x11,
-    DUAL_2 = 0x12,
-    DUAL_6 = 0x13,
-    DUAL_12 = 0x14,
-    DUAL_16 = 0x15,
-    DUAL_26 = 0x16,
-};
-
-enum class SEX : uint8_t
-{
-    FEMALE = 0,
-    MALE = 1,
-    // For items. This is also used as error, sometime.
-    NEUTRAL = 2,
-};
-inline
-char sex_to_char(SEX sex)
-{
-    switch (sex)
-    {
-    case SEX::FEMALE: return 'F';
-    case SEX::MALE: return 'M';
-    default: return '\0';
-    }
-}
-inline
-SEX sex_from_char(char c)
-{
-    switch (c)
-    {
-    case 'F': return SEX::FEMALE;
-    case 'M': return SEX::MALE;
-    default: return SEX::NEUTRAL;
-    }
-}
-
-struct CharKey
-{
-    CharName name;
-    int account_id;
-    int char_id;
-    unsigned char char_num;
-};
-
-struct CharData
-{
-    int partner_id;
-
-    int base_exp, job_exp, zeny;
-
-    short species;
-    short status_point, skill_point;
-    int hp, max_hp, sp, max_sp;
-    Option option;
-    short karma, manner;
-    short hair, hair_color, clothes_color;
-    int party_id;
-
-    ItemLook weapon;
-    short shield;
-    short head_top, head_mid, head_bottom;
-
-    unsigned char base_level, job_level;
-    earray<short, ATTR, ATTR::COUNT> attrs;
-    SEX sex;
-
-    unsigned long mapip;
-    unsigned int mapport;
-
-    struct point last_point, save_point;
-    struct item inventory[MAX_INVENTORY];
-    earray<skill_value, SkillID, MAX_SKILL> skill;
-    int global_reg_num;
-    struct global_reg global_reg[GLOBAL_REG_NUM];
-    int account_reg_num;
-    struct global_reg account_reg[ACCOUNT_REG_NUM];
-    int account_reg2_num;
-    struct global_reg account_reg2[ACCOUNT_REG2_NUM];
-};
-
-struct CharPair
-{
-    CharKey key;
-    std::unique_ptr<CharData> data;
-
-    CharPair()
-    : key{}, data(make_unique<CharData>())
-    {}
-};
-
-struct storage
-{
-    int dirty;
-    int account_id;
-    short storage_status;
-    short storage_amount;
-    struct item storage_[MAX_STORAGE];
-};
-
-struct map_session_data;
-
-struct GM_Account
-{
-    int account_id;
-    uint8_t level;
-};
-
-struct party_member
-{
-    int account_id;
-    CharName name;
-    MapName map;
-    int leader, online, lv;
-    struct map_session_data *sd;
-};
-
-struct party
-{
-    int party_id;
-    PartyName name;
-    int exp;
-    int item;
-    struct party_member member[MAX_PARTY];
-};
-
-#endif // MMO_HPP
diff --git a/src/common/nullpo.cpp b/src/common/nullpo.cpp
deleted file mode 100644
index 423ed8c..0000000
--- a/src/common/nullpo.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "nullpo.hpp"
-
-#include <cstdio>
-
-#include "../poison.hpp"
-
-/// Actual output function
-static
-void nullpo_info(const char *file, int line, const char *func)
-{
-    if (!file)
-        file = "??";
-    if (!func || !*func)
-        func = "unknown";
-
-    fprintf(stderr, "%s:%d: in func `%s': NULL pointer\n",
-            file, line, func);
-}
-
-bool nullpo_chk(const char *file, int line, const char *func,
-                 const void *target)
-{
-    if (target)
-        return 0;
-
-    nullpo_info(file, line, func);
-    return 1;
-}
diff --git a/src/common/nullpo.hpp b/src/common/nullpo.hpp
deleted file mode 100644
index 0eaa1b2..0000000
--- a/src/common/nullpo.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/// return wrappers for unexpected NULL pointers
-#ifndef NULLPO_HPP
-#define NULLPO_HPP
-/// Uncomment this to live dangerously
-/// (really exist to detect mostly-unused variables)
-//# define BUG_FREE
-
-/// All functions print to standard error (was: standard output)
-/// nullpo_ret(cond) - return 0 if given pointer is NULL
-/// nullpo_retv(cond) - just return (function returns void)
-/// nullpo_retr(rv, cond) - return given value instead
-
-# ifndef BUG_FREE
-#  define nullpo_retr(ret, t)                                   \
-    if (nullpo_chk(__FILE__, __LINE__, __PRETTY_FUNCTION__, t)) \
-        return ret;
-# else // BUG_FREE
-#  define nullpo_retr(ret, t) /*t*/
-# endif // BUG_FREE
-
-# define nullpo_ret(t) nullpo_retr(0, t)
-# define nullpo_retv(t) nullpo_retr(, t)
-
-# include "../sanity.hpp"
-
-/// Used by macros in this header
-bool nullpo_chk(const char *file, int line, const char *func,
-        const void *target);
-
-template<class T>
-bool nullpo_chk(const char *file, int line, const char *func, T target)
-{
-    return nullpo_chk(file, line, func, target.operator->());
-}
-template<class T>
-bool nullpo_chk(const char *file, int line, const char *func, T *target)
-{
-    return nullpo_chk(file, line, func, static_cast<const void *>(target));
-}
-
-#endif // NULLPO_HPP
diff --git a/src/common/operators.hpp b/src/common/operators.hpp
deleted file mode 100644
index 3d44b81..0000000
--- a/src/common/operators.hpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#ifndef OPERATORS_HPP
-#define OPERATORS_HPP
-
-namespace _operators
-{
-    class Comparable {};
-
-    template<class T>
-    bool operator == (T l, T r)
-    {
-        return l.value == r.value;
-    }
-
-    template<class T>
-    bool operator != (T l, T r)
-    {
-        return l.value != r.value;
-    }
-
-    template<class T>
-    bool operator < (T l, T r)
-    {
-        return l.value < r.value;
-    }
-
-    template<class T>
-    bool operator <= (T l, T r)
-    {
-        return l.value <= r.value;
-    }
-
-    template<class T>
-    bool operator > (T l, T r)
-    {
-        return l.value > r.value;
-    }
-
-    template<class T>
-    bool operator >= (T l, T r)
-    {
-        return l.value >= r.value;
-    }
-}
-
-using _operators::Comparable;
-
-#endif // OPERATORS_HPP
diff --git a/src/common/random.cpp b/src/common/random.cpp
deleted file mode 100644
index 273dcec..0000000
--- a/src/common/random.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include "random2.hpp"
-
-#include "../poison.hpp"
-
-namespace random_
-{
-    std::mt19937 generate{std::random_device()()};
-} // namespace random_
diff --git a/src/common/random.hpp b/src/common/random.hpp
deleted file mode 100644
index a694cce..0000000
--- a/src/common/random.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef RANDOM_HPP
-#define RANDOM_HPP
-
-# include "random.t.hpp"
-
-# include "../sanity.hpp"
-
-# include <random>
-
-// This is not namespace random since that collides with a C function,
-// but this can be revisited when everything goes into namespace tmwa.
-namespace random_
-{
-    /// Get a random number from 0 .. 2**32 - 1
-    extern std::mt19937 generate;
-
-    /// Get a random number from 0 .. bound - 1
-    inline
-    int to(int bound)
-    {
-        std::uniform_int_distribution<int> dist(0, bound - 1);
-        return dist(generate);
-    }
-
-    /// Get a random number from low .. high (inclusive!)
-    inline
-    int in(int low, int high)
-    {
-        std::uniform_int_distribution<int> dist(low, high);
-        return dist(generate);
-    }
-
-    inline
-    bool coin()
-    {
-        // sigh, can't specify <bool> directly ...
-        std::uniform_int_distribution<int> dist(false, true);
-        return dist(generate);
-    }
-
-    inline
-    bool chance(Fraction f)
-    {
-        if (f.num <= 0)
-            return false;
-        if (f.num >= f.den)
-            return true;
-        return random_::to(f.den) < f.num;
-    }
-
-    // C is usually one of:
-    //  std::vector<T>
-    //  std::initializer_list<T>
-    //  std::array<T, n>
-    template<class C>
-    auto choice(C&& c) -> decltype(*c.begin())
-    {
-        return *(c.begin() + random_::to(c.size()));
-    }
-
-    // allow bare braces
-    template<class T>
-    T choice(std::initializer_list<T>&& il)
-    {
-        return random_::choice(il);
-    }
-} // namespace random_
-
-#endif // RANDOM_HPP
diff --git a/src/common/random.t.hpp b/src/common/random.t.hpp
deleted file mode 100644
index 98a6c59..0000000
--- a/src/common/random.t.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef RANDOM_T_HPP
-#define RANDOM_T_HPP
-
-namespace random_
-{
-    struct Fraction
-    {
-        int num, den;
-    };
-
-    template<class T, T den>
-    struct Fixed
-    {
-        T num;
-
-        operator Fraction()
-        {
-            return {num, den};
-        }
-    };
-} // namespace random_
-
-#endif // RANDOM_T_HPP
diff --git a/src/common/random2.hpp b/src/common/random2.hpp
deleted file mode 100644
index 86deddf..0000000
--- a/src/common/random2.hpp
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef RANDOM2_HPP
-#define RANDOM2_HPP
-
-# include "random.hpp"
-# include "utils2.hpp"
-
-# include <algorithm>
-
-namespace random_
-{
-    namespace detail
-    {
-        struct RandomIterator
-        {
-            int bound;
-            int current;
-            bool frist;
-
-            void operator ++()
-            {
-                frist = false;
-                // TODO: reimplement in terms of LFSRs, so that certain
-                // blockage patterns don't favor adjacent cells.
-                current = current + 1;
-                if (current == bound)
-                    current = 0;
-            }
-            int operator *()
-            {
-                return current;
-            }
-        };
-
-        inline
-        bool operator == (RandomIterator l, RandomIterator r)
-        {
-            return l.current == r.current && l.frist == r.frist;
-        }
-
-        inline
-        bool operator != (RandomIterator l, RandomIterator r)
-        {
-            return !(l == r);
-        }
-    }
-
-    /// Yield every cell from 0 .. bound - 1 in some order.
-    /// The starting position is random, but not the order itself.
-    ///
-    /// Expected usage:
-    ///     for (int i : random_::iterator(vec.size()))
-    ///         if (vec[i].okay())
-    ///             return frob(vec[i]);
-    inline
-    IteratorPair<detail::RandomIterator> iterator(int bound)
-    {
-        int current = random_::to(bound);
-        return
-        {
-            detail::RandomIterator{bound, current, true},
-            detail::RandomIterator{bound, current, false}
-        };
-    }
-
-    /// similar to std::random_shuffle(c.begin(), c.end()), but guaranteed
-    /// to use a good source of randomness.
-    template<class C>
-    void shuffle(C&& c)
-    {
-        std::random_shuffle(c.begin(), c.end(), random_::to);
-    }
-} // namespace random_
-
-#endif // RANDOM2_HPP
diff --git a/src/common/socket.cpp b/src/common/socket.cpp
deleted file mode 100644
index 73e32a4..0000000
--- a/src/common/socket.cpp
+++ /dev/null
@@ -1,474 +0,0 @@
-#include "socket.hpp"
-
-#include <arpa/inet.h>
-#include <netinet/tcp.h>
-#include <sys/socket.h>
-//#include <sys/types.h>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <cstdlib>
-#include <cstring>
-#include <ctime>
-
-#include "../io/cxxstdio.hpp"
-#include "core.hpp"
-#include "timer.hpp"
-#include "utils.hpp"
-
-#include "../poison.hpp"
-
-static
-io::FD_Set readfds;
-static
-int fd_max;
-
-static
-const uint32_t RFIFO_SIZE = 65536;
-static
-const uint32_t WFIFO_SIZE = 65536;
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-static
-std::array<std::unique_ptr<Session>, FD_SETSIZE> session;
-#pragma GCC diagnostic pop
-
-void set_session(io::FD fd, std::unique_ptr<Session> sess)
-{
-    int f = fd.uncast_dammit();
-    assert (0 <= f && f < FD_SETSIZE);
-    session[f] = std::move(sess);
-}
-Session *get_session(io::FD fd)
-{
-    int f = fd.uncast_dammit();
-    if (0 <= f && f < FD_SETSIZE)
-        return session[f].get();
-    return nullptr;
-}
-void reset_session(io::FD fd)
-{
-    int f = fd.uncast_dammit();
-    assert (0 <= f && f < FD_SETSIZE);
-    session[f] = nullptr;
-}
-int get_fd_max() { return fd_max; }
-IteratorPair<ValueIterator<io::FD, IncrFD>> iter_fds()
-{
-    return {io::FD::cast_dammit(0), io::FD::cast_dammit(fd_max)};
-}
-
-/// clean up by discarding handled bytes
-inline
-void RFIFOFLUSH(Session *s)
-{
-    really_memmove(&s->rdata[0], &s->rdata[s->rdata_pos], RFIFOREST(s));
-    s->rdata_size = RFIFOREST(s);
-    s->rdata_pos = 0;
-}
-
-/// how much room there is to read more data
-inline
-size_t RFIFOSPACE(Session *s)
-{
-    return s->max_rdata - s->rdata_size;
-}
-
-
-/// Discard all input
-static
-void null_parse(Session *s);
-/// Default parser for new connections
-static
-void (*default_func_parse)(Session *) = null_parse;
-
-void set_defaultparse(void (*defaultparse)(Session *))
-{
-    default_func_parse = defaultparse;
-}
-
-/// Read from socket to the queue
-static
-void recv_to_fifo(Session *s)
-{
-    if (s->eof)
-        return;
-
-    ssize_t len = s->fd.read(&s->rdata[s->rdata_size],
-                        RFIFOSPACE(s));
-
-    if (len > 0)
-    {
-        s->rdata_size += len;
-        s->connected = 1;
-    }
-    else
-    {
-        s->eof = 1;
-    }
-}
-
-static
-void send_from_fifo(Session *s)
-{
-    if (s->eof)
-        return;
-
-    ssize_t len = s->fd.write(&s->wdata[0], s->wdata_size);
-
-    if (len > 0)
-    {
-        s->wdata_size -= len;
-        if (s->wdata_size)
-        {
-            really_memmove(&s->wdata[0], &s->wdata[len],
-                     s->wdata_size);
-        }
-        s->connected = 1;
-    }
-    else
-    {
-        s->eof = 1;
-    }
-}
-
-static
-void null_parse(Session *s)
-{
-    PRINTF("null_parse : %d\n", s);
-    RFIFOSKIP(s, RFIFOREST(s));
-}
-
-
-static
-void connect_client(Session *ls)
-{
-    struct sockaddr_in client_address;
-    socklen_t len = sizeof(client_address);
-
-    io::FD fd = ls->fd.accept(reinterpret_cast<struct sockaddr *>(&client_address), &len);
-    if (fd == io::FD())
-    {
-        perror("accept");
-        return;
-    }
-    if (fd.uncast_dammit() >= SOFT_LIMIT)
-    {
-        FPRINTF(stderr, "softlimit reached, disconnecting : %d\n", fd.uncast_dammit());
-        fd.shutdown(SHUT_RDWR);
-        fd.close();
-        return;
-    }
-    if (fd_max <= fd.uncast_dammit())
-    {
-        fd_max = fd.uncast_dammit() + 1;
-    }
-
-    const int yes = 1;
-    /// Allow to bind() again after the server restarts.
-    // Since the socket is still in the TIME_WAIT, there's a possibility
-    // that formerly lost packets might be delivered and confuse the server.
-    fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
-    /// Send packets as soon as possible
-    /// even if the kernel thinks there is too little for it to be worth it!
-    /// Testing shows this is indeed a good idea.
-    fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);
-
-    // Linux-ism: Set socket options to optimize for thin streams
-    // See http://lwn.net/Articles/308919/ and
-    // Documentation/networking/tcp-thin.txt .. Kernel 3.2+
-#ifdef TCP_THIN_LINEAR_TIMEOUTS
-    fd.setsockopt(IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof yes);
-#endif
-#ifdef TCP_THIN_DUPACK
-    fd.setsockopt(IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof yes);
-#endif
-
-    readfds.set(fd);
-
-    fd.fcntl(F_SETFL, O_NONBLOCK);
-
-    set_session(fd, make_unique<Session>());
-    Session *s = get_session(fd);
-    s->fd = fd;
-    s->rdata.new_(RFIFO_SIZE);
-    s->wdata.new_(WFIFO_SIZE);
-
-    s->max_rdata = RFIFO_SIZE;
-    s->max_wdata = WFIFO_SIZE;
-    s->func_recv = recv_to_fifo;
-    s->func_send = send_from_fifo;
-    s->func_parse = default_func_parse;
-    s->client_ip = IP4Address(client_address.sin_addr);
-    s->created = TimeT::now();
-    s->connected = 0;
-}
-
-Session *make_listen_port(uint16_t port)
-{
-    struct sockaddr_in server_address;
-    io::FD fd = io::FD::socket(AF_INET, SOCK_STREAM, 0);
-    if (fd == io::FD())
-    {
-        perror("socket");
-        return nullptr;
-    }
-    if (fd_max <= fd.uncast_dammit())
-        fd_max = fd.uncast_dammit() + 1;
-
-    fd.fcntl(F_SETFL, O_NONBLOCK);
-
-    const int yes = 1;
-    /// Allow to bind() again after the server restarts.
-    // Since the socket is still in the TIME_WAIT, there's a possibility
-    // that formerly lost packets might be delivered and confuse the server.
-    fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
-    /// Send packets as soon as possible
-    /// even if the kernel thinks there is too little for it to be worth it!
-    // I'm not convinced this is a good idea; although in minimizes the
-    // latency for an individual write, it increases traffic in general.
-    fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);
-
-    server_address.sin_family = AF_INET;
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-#if __GNUC__ > 4 || __GNUC_MINOR__ >= 8
-# pragma GCC diagnostic ignored "-Wuseless-cast"
-#endif
-    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
-    server_address.sin_port = htons(port);
-#pragma GCC diagnostic pop
-
-    if (fd.bind(reinterpret_cast<struct sockaddr *>(&server_address),
-              sizeof(server_address)) == -1)
-    {
-        perror("bind");
-        exit(1);
-    }
-    if (fd.listen(5) == -1)
-    {                           /* error */
-        perror("listen");
-        exit(1);
-    }
-
-    readfds.set(fd);
-
-    set_session(fd, make_unique<Session>());
-    Session *s = get_session(fd);
-    s->fd = fd;
-
-    s->func_recv = connect_client;
-    s->created = TimeT::now();
-    s->connected = 1;
-
-    return s;
-}
-
-Session *make_connection(IP4Address ip, uint16_t port)
-{
-    struct sockaddr_in server_address;
-    io::FD fd = io::FD::socket(AF_INET, SOCK_STREAM, 0);
-    if (fd == io::FD())
-    {
-        perror("socket");
-        return nullptr;
-    }
-    if (fd_max <= fd.uncast_dammit())
-        fd_max = fd.uncast_dammit() + 1;
-
-    const int yes = 1;
-    /// Allow to bind() again after the server restarts.
-    // Since the socket is still in the TIME_WAIT, there's a possibility
-    // that formerly lost packets might be delivered and confuse the server.
-    fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
-    /// Send packets as soon as possible
-    /// even if the kernel thinks there is too little for it to be worth it!
-    // I'm not convinced this is a good idea; although in minimizes the
-    // latency for an individual write, it increases traffic in general.
-    fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);
-
-    server_address.sin_family = AF_INET;
-    server_address.sin_addr = in_addr(ip);
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wold-style-cast"
-#if __GNUC__ > 4 || __GNUC_MINOR__ >= 8
-# pragma GCC diagnostic ignored "-Wuseless-cast"
-#endif
-    server_address.sin_port = htons(port);
-#pragma GCC diagnostic pop
-
-    fd.fcntl(F_SETFL, O_NONBLOCK);
-
-    /// Errors not caught - we must not block
-    /// Let the main select() loop detect when we know the state
-    fd.connect(reinterpret_cast<struct sockaddr *>(&server_address),
-             sizeof(struct sockaddr_in));
-
-    readfds.set(fd);
-
-    set_session(fd, make_unique<Session>());
-    Session *s = get_session(fd);
-    s->fd = fd;
-    s->rdata.new_(RFIFO_SIZE);
-    s->wdata.new_(WFIFO_SIZE);
-
-    s->max_rdata = RFIFO_SIZE;
-    s->max_wdata = WFIFO_SIZE;
-    s->func_recv = recv_to_fifo;
-    s->func_send = send_from_fifo;
-    s->func_parse = default_func_parse;
-    s->created = TimeT::now();
-    s->connected = 1;
-
-    return s;
-}
-
-void delete_session(Session *s)
-{
-    if (!s)
-        return;
-
-    io::FD fd = s->fd;
-    // If this was the highest fd, decrease it
-    // We could add a loop to decrement fd_max further for every null session,
-    // but this is cheap and good enough for the typical case
-    if (fd.uncast_dammit() == fd_max - 1)
-        fd_max--;
-    readfds.clr(fd);
-    {
-        s->rdata.delete_();
-        s->wdata.delete_();
-        s->session_data.reset();
-        reset_session(fd);
-    }
-
-    // just close() would try to keep sending buffers
-    fd.shutdown(SHUT_RDWR);
-    fd.close();
-}
-
-void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size)
-{
-    if (s->max_rdata != rfifo_size && s->rdata_size < rfifo_size)
-    {
-        s->rdata.resize(rfifo_size);
-        s->max_rdata = rfifo_size;
-    }
-    if (s->max_wdata != wfifo_size && s->wdata_size < wfifo_size)
-    {
-        s->wdata.resize(wfifo_size);
-        s->max_wdata = wfifo_size;
-    }
-}
-
-void WFIFOSET(Session *s, size_t len)
-{
-    if (s->wdata_size + len + 16384 > s->max_wdata)
-    {
-        realloc_fifo(s, s->max_rdata, s->max_wdata << 1);
-        PRINTF("socket: %d wdata expanded to %zu bytes.\n", s, s->max_wdata);
-    }
-    if (s->wdata_size + len + 2048 < s->max_wdata)
-        s->wdata_size += len;
-    else
-        FPRINTF(stderr, "socket: %d wdata lost !!\n", s), abort();
-}
-
-void do_sendrecv(interval_t next_ms)
-{
-    bool any = false;
-    io::FD_Set rfd = readfds, wfd;
-    for (io::FD i : iter_fds())
-    {
-        Session *s = get_session(i);
-        if (s)
-        {
-            any = true;
-            if (s->wdata_size)
-                wfd.set(i);
-        }
-    }
-    if (!any)
-    {
-        if (!has_timers())
-        {
-            PRINTF("Shutting down - nothing to do\n");
-            runflag = false;
-        }
-        return;
-    }
-    struct timeval timeout;
-    {
-        std::chrono::seconds next_s = std::chrono::duration_cast<std::chrono::seconds>(next_ms);
-        std::chrono::microseconds next_us = next_ms - next_s;
-        timeout.tv_sec = next_s.count();
-        timeout.tv_usec = next_us.count();
-    }
-    if (io::FD_Set::select(fd_max, &rfd, &wfd, NULL, &timeout) <= 0)
-        return;
-    for (io::FD i : iter_fds())
-    {
-        Session *s = get_session(i);
-        if (!s)
-            continue;
-        if (wfd.isset(i))
-        {
-            if (s->func_send)
-                //send_from_fifo(i);
-                s->func_send(s);
-        }
-        if (rfd.isset(i))
-        {
-            if (s->func_recv)
-                //recv_to_fifo(i);
-                //or connect_client(i);
-                s->func_recv(s);
-        }
-    }
-}
-
-void do_parsepacket(void)
-{
-    for (io::FD i : iter_fds())
-    {
-        Session *s = get_session(i);
-        if (!s)
-            continue;
-        if (!s->connected
-            && static_cast<time_t>(TimeT::now()) - static_cast<time_t>(s->created) > CONNECT_TIMEOUT)
-        {
-            PRINTF("Session #%d timed out\n", s);
-            s->eof = 1;
-        }
-        if (!s->rdata_size && !s->eof)
-            continue;
-        if (s->func_parse)
-        {
-            s->func_parse(s);
-            /// some func_parse may call delete_session
-            s = get_session(i);
-            if (s && s->eof)
-            {
-                delete_session(s);
-                s = nullptr;
-            }
-            if (!s)
-                continue;
-        }
-        /// Reclaim buffer space for what was read
-        RFIFOFLUSH(s);
-    }
-}
-
-void RFIFOSKIP(Session *s, size_t len)
-{
-    s->rdata_pos += len;
-
-    if (s->rdata_size < s->rdata_pos)
-    {
-        FPRINTF(stderr, "too many skip\n");
-        abort();
-    }
-}
diff --git a/src/common/socket.hpp b/src/common/socket.hpp
deleted file mode 100644
index e0847ac..0000000
--- a/src/common/socket.hpp
+++ /dev/null
@@ -1,371 +0,0 @@
-#ifndef SOCKET_HPP
-#define SOCKET_HPP
-
-# include "../sanity.hpp"
-
-# include <netinet/in.h>
-
-# include <cstdio>
-
-# include <array>
-
-# include "../strings/astring.hpp"
-# include "../strings/vstring.hpp"
-# include "../strings/xstring.hpp"
-
-# include "../io/fd.hpp"
-
-# include "dumb_ptr.hpp"
-# include "ip.hpp"
-# include "utils.hpp"
-# include "timer.t.hpp"
-
-struct SessionData
-{
-};
-struct SessionDeleter
-{
-    // defined per-server
-    void operator()(SessionData *sd);
-};
-
-// Struct declaration
-
-struct Session
-{
-    /// Checks whether a newly-connected socket actually does anything
-    TimeT created;
-    bool connected;
-
-    /// Flag needed since structure must be freed in a server-dependent manner
-    bool eof;
-
-    /// Since this is a single-threaded application, it can't block
-    /// These are the read/write queues
-    dumb_ptr<uint8_t[]> rdata, wdata;
-    size_t max_rdata, max_wdata;
-    /// How much is actually in the queue
-    size_t rdata_size, wdata_size;
-    /// How much has already been read from the queue
-    /// Note that there is no need for a wdata_pos
-    size_t rdata_pos;
-
-    IP4Address client_ip;
-
-    /// Send or recieve
-    /// Only called when select() indicates the socket is ready
-    /// If, after that, nothing is read, it sets eof
-    // These could probably be hard-coded with a little work
-    void (*func_recv)(Session *);
-    void (*func_send)(Session *);
-    /// This is the important one
-    /// Set to different functions depending on whether the connection
-    /// is a player or a server/ladmin
-    /// Can be set explicitly or via set_defaultparse
-    void (*func_parse)(Session *);
-    /// Server-specific data type
-    std::unique_ptr<SessionData, SessionDeleter> session_data;
-
-    io::FD fd;
-};
-
-inline
-int convert_for_printf(Session *s)
-{
-    return s->fd.uncast_dammit();
-}
-
-// save file descriptors for important stuff
-constexpr int SOFT_LIMIT = FD_SETSIZE - 50;
-
-// socket timeout to establish a full connection in seconds
-constexpr int CONNECT_TIMEOUT = 15;
-
-
-void set_session(io::FD fd, std::unique_ptr<Session> sess);
-Session *get_session(io::FD fd);
-void reset_session(io::FD fd);
-int get_fd_max();
-
-class IncrFD
-{
-public:
-    static
-    io::FD inced(io::FD v)
-    {
-        return io::FD::cast_dammit(v.uncast_dammit() + 1);
-    }
-};
-IteratorPair<ValueIterator<io::FD, IncrFD>> iter_fds();
-
-
-/// open a socket, bind, and listen. Return an fd, or -1 if socket() fails,
-/// but exit if bind() or listen() fails
-Session *make_listen_port(uint16_t port);
-/// Connect to an address, return a connected socket or -1
-// FIXME - this is IPv4 only!
-Session *make_connection(IP4Address ip, uint16_t port);
-/// free() the structure and close() the fd
-void delete_session(Session *);
-/// Make a the internal queues bigger
-void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size);
-/// Update all sockets that can be read/written from the queues
-void do_sendrecv(interval_t next);
-/// Call the parser function for every socket that has read data
-void do_parsepacket(void);
-
-/// Change the default parser for newly connected clients
-// typically called once per server, but individual clients may identify
-// themselves as servers
-void set_defaultparse(void(*defaultparse)(Session *));
-
-template<class T>
-uint8_t *pod_addressof_m(T& structure)
-{
-    static_assert(is_trivially_copyable<T>::value, "Can only byte-copy POD-ish structs");
-    return &reinterpret_cast<uint8_t&>(structure);
-}
-
-template<class T>
-const uint8_t *pod_addressof_c(const T& structure)
-{
-    static_assert(is_trivially_copyable<T>::value, "Can only byte-copy POD-ish structs");
-    return &reinterpret_cast<const uint8_t&>(structure);
-}
-
-
-/// Check how much can be read
-inline
-size_t RFIFOREST(Session *s)
-{
-    return s->rdata_size - s->rdata_pos;
-}
-/// Read from the queue
-inline
-const void *RFIFOP(Session *s, size_t pos)
-{
-    return &s->rdata[s->rdata_pos + pos];
-}
-inline
-uint8_t RFIFOB(Session *s, size_t pos)
-{
-    return *static_cast<const uint8_t *>(RFIFOP(s, pos));
-}
-inline
-uint16_t RFIFOW(Session *s, size_t pos)
-{
-    return *static_cast<const uint16_t *>(RFIFOP(s, pos));
-}
-inline
-uint32_t RFIFOL(Session *s, size_t pos)
-{
-    return *static_cast<const uint32_t *>(RFIFOP(s, pos));
-}
-template<class T>
-void RFIFO_STRUCT(Session *s, size_t pos, T& structure)
-{
-    really_memcpy(pod_addressof_m(structure), static_cast<const uint8_t *>(RFIFOP(s, pos)), sizeof(T));
-}
-inline
-IP4Address RFIFOIP(Session *s, size_t pos)
-{
-    IP4Address o;
-    RFIFO_STRUCT(s, pos, o);
-    return o;
-}
-template<uint8_t len>
-inline
-VString<len-1> RFIFO_STRING(Session *s, size_t pos)
-{
-    const char *const begin = static_cast<const char *>(RFIFOP(s, pos));
-    const char *const end = begin + len-1;
-    const char *const mid = std::find(begin, end, '\0');
-    return XString(begin, mid, nullptr);
-}
-inline
-AString RFIFO_STRING(Session *s, size_t pos, size_t len)
-{
-    const char *const begin = static_cast<const char *>(RFIFOP(s, pos));
-    const char *const end = begin + len;
-    const char *const mid = std::find(begin, end, '\0');
-    return XString(begin, mid, nullptr);
-}
-inline
-void RFIFO_BUF_CLONE(Session *s, uint8_t *buf, size_t len)
-{
-    really_memcpy(buf, static_cast<const uint8_t *>(RFIFOP(s, 0)), len);
-}
-
-/// Done reading
-void RFIFOSKIP(Session *s, size_t len);
-
-/// Read from an arbitrary buffer
-inline
-const void *RBUFP(const uint8_t *p, size_t pos)
-{
-    return p + pos;
-}
-inline
-uint8_t RBUFB(const uint8_t *p, size_t pos)
-{
-    return *static_cast<const uint8_t *>(RBUFP(p, pos));
-}
-inline
-uint16_t RBUFW(const uint8_t *p, size_t pos)
-{
-    return *static_cast<const uint16_t *>(RBUFP(p, pos));
-}
-inline
-uint32_t RBUFL(const uint8_t *p, size_t pos)
-{
-    return *static_cast<const uint32_t *>(RBUFP(p, pos));
-}
-template<class T>
-void RBUF_STRUCT(const uint8_t *p, size_t pos, T& structure)
-{
-    really_memcpy(pod_addressof_m(structure), p + pos, sizeof(T));
-}
-inline
-IP4Address RBUFIP(const uint8_t *p, size_t pos)
-{
-    IP4Address o;
-    RBUF_STRUCT(p, pos, o);
-    return o;
-}
-template<uint8_t len>
-inline
-VString<len-1> RBUF_STRING(const uint8_t *p, size_t pos)
-{
-    const char *const begin = static_cast<const char *>(RBUFP(p, pos));
-    const char *const end = begin + len-1;
-    const char *const mid = std::find(begin, end, '\0');
-    return XString(begin, mid, nullptr);
-}
-inline
-AString RBUF_STRING(const uint8_t *p, size_t pos, size_t len)
-{
-    const char *const begin = static_cast<const char *>(RBUFP(p, pos));
-    const char *const end = begin + len;
-    const char *const mid = std::find(begin, end, '\0');
-    return XString(begin, mid, nullptr);
-}
-
-
-/// Unused - check how much data can be written
-// the existence of this seems scary
-inline
-size_t WFIFOSPACE(Session *s)
-{
-    return s->max_wdata - s->wdata_size;
-}
-/// Write to the queue
-inline
-void *WFIFOP(Session *s, size_t pos)
-{
-    return &s->wdata[s->wdata_size + pos];
-}
-inline
-uint8_t& WFIFOB(Session *s, size_t pos)
-{
-    return *static_cast<uint8_t *>(WFIFOP(s, pos));
-}
-inline
-uint16_t& WFIFOW(Session *s, size_t pos)
-{
-    return *static_cast<uint16_t *>(WFIFOP(s, pos));
-}
-inline
-uint32_t& WFIFOL(Session *s, size_t pos)
-{
-    return *static_cast<uint32_t *>(WFIFOP(s, pos));
-}
-template<class T>
-void WFIFO_STRUCT(Session *s, size_t pos, T& structure)
-{
-    really_memcpy(static_cast<uint8_t *>(WFIFOP(s, pos)), pod_addressof_c(structure), sizeof(T));
-}
-inline
-IP4Address& WFIFOIP(Session *s, size_t pos)
-{
-    static_assert(is_trivially_copyable<IP4Address>::value, "That was the whole point");
-    return *static_cast<IP4Address *>(WFIFOP(s, pos));
-}
-inline
-void WFIFO_STRING(Session *s, size_t pos, XString str, size_t len)
-{
-    char *const begin = static_cast<char *>(WFIFOP(s, pos));
-    char *const end = begin + len;
-    char *const mid = std::copy(str.begin(), str.end(), begin);
-    std::fill(mid, end, '\0');
-}
-inline
-void WFIFO_ZERO(Session *s, size_t pos, size_t len)
-{
-    uint8_t *b = static_cast<uint8_t *>(WFIFOP(s, pos));
-    uint8_t *e = b + len;
-    std::fill(b, e, '\0');
-}
-inline
-void WFIFO_BUF_CLONE(Session *s, const uint8_t *buf, size_t len)
-{
-    really_memcpy(static_cast<uint8_t *>(WFIFOP(s, 0)), buf, len);
-}
-
-/// Finish writing
-void WFIFOSET(Session *s, size_t len);
-
-/// Write to an arbitrary buffer
-inline
-void *WBUFP(uint8_t *p, size_t pos)
-{
-    return p + pos;
-}
-inline
-uint8_t& WBUFB(uint8_t *p, size_t pos)
-{
-    return *static_cast<uint8_t *>(WBUFP(p, pos));
-}
-inline
-uint16_t& WBUFW(uint8_t *p, size_t pos)
-{
-    return *static_cast<uint16_t *>(WBUFP(p, pos));
-}
-inline
-uint32_t& WBUFL(uint8_t *p, size_t pos)
-{
-    return *static_cast<uint32_t *>(WBUFP(p, pos));
-}
-template<class T>
-void WBUF_STRUCT(uint8_t *p, size_t pos, T& structure)
-{
-    really_memcpy(p + pos, pod_addressof_c(structure), sizeof(T));
-}
-inline
-IP4Address& WBUFIP(uint8_t *p, size_t pos)
-{
-    return *static_cast<IP4Address *>(WBUFP(p, pos));
-}
-inline
-void WBUF_STRING(uint8_t *p, size_t pos, XString s, size_t len)
-{
-    char *const begin = static_cast<char *>(WBUFP(p, pos));
-    char *const end = begin + len;
-    char *const mid = std::copy(s.begin(), s.end(), begin);
-    std::fill(mid, end, '\0');
-}
-inline
-void WBUF_ZERO(uint8_t *p, size_t pos, size_t len)
-{
-    uint8_t *b = static_cast<uint8_t *>(WBUFP(p, pos));
-    uint8_t *e = b + len;
-    std::fill(b, e, '\0');
-}
-
-inline
-void RFIFO_WFIFO_CLONE(Session *rs, Session *ws, size_t len)
-{
-    really_memcpy(static_cast<uint8_t *>(WFIFOP(ws, 0)),
-            static_cast<const uint8_t *>(RFIFOP(rs, 0)), len);
-}
-
-#endif // SOCKET_HPP
diff --git a/src/common/timer.cpp b/src/common/timer.cpp
deleted file mode 100644
index b5a2a5e..0000000
--- a/src/common/timer.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-#include "timer.hpp"
-
-#include <sys/stat.h>
-#include <sys/time.h>
-
-#include <cassert>
-#include <cstring>
-
-#include <queue>
-
-#include "../strings/zstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "utils.hpp"
-
-#include "../poison.hpp"
-
-struct TimerData
-{
-    /// This will be reset on call, to avoid problems.
-    Timer *owner;
-
-    /// When it will be triggered
-    tick_t tick;
-    /// What will be done
-    timer_func func;
-    /// Repeat rate - 0 for oneshot
-    interval_t interval;
-
-    TimerData(Timer *o, tick_t t, timer_func f, interval_t i)
-    : owner(o)
-    , tick(t)
-    , func(std::move(f))
-    , interval(i)
-    {}
-};
-
-struct TimerCompare
-{
-    /// implement "less than"
-    bool operator() (dumb_ptr<TimerData> l, dumb_ptr<TimerData> r)
-    {
-        // C++ provides a max-heap, but we want
-        // the smallest tick to be the head (a min-heap).
-        return l->tick > r->tick;
-    }
-};
-
-static
-std::priority_queue<dumb_ptr<TimerData>, std::vector<dumb_ptr<TimerData>>, TimerCompare> timer_heap;
-
-
-tick_t gettick_cache;
-
-tick_t milli_clock::now(void) noexcept
-{
-    struct timeval tval;
-    // BUG: This will cause strange behavior if the system clock is changed!
-    // it should be reimplemented in terms of clock_gettime(CLOCK_MONOTONIC, )
-    gettimeofday(&tval, NULL);
-    return gettick_cache = tick_t(std::chrono::seconds(tval.tv_sec)
-            + std::chrono::duration_cast<std::chrono::milliseconds>(
-                std::chrono::microseconds(tval.tv_usec)));
-}
-
-static
-void do_nothing(TimerData *, tick_t)
-{
-}
-
-void Timer::cancel()
-{
-    if (!td)
-        return;
-
-    assert (this == td->owner);
-    td->owner = nullptr;
-    td->func = do_nothing;
-    td->interval = interval_t::zero();
-    td = nullptr;
-}
-
-void Timer::detach()
-{
-    assert (this == td->owner);
-    td->owner = nullptr;
-    td = nullptr;
-}
-
-static
-void push_timer_heap(dumb_ptr<TimerData> td)
-{
-    timer_heap.push(td);
-}
-
-static
-dumb_ptr<TimerData> top_timer_heap(void)
-{
-    if (timer_heap.empty())
-        return dumb_ptr<TimerData>();
-    return timer_heap.top();
-}
-
-static
-void pop_timer_heap(void)
-{
-    timer_heap.pop();
-}
-
-Timer::Timer(tick_t tick, timer_func func, interval_t interval)
-: td(dumb_ptr<TimerData>::make(this, tick, std::move(func), interval))
-{
-    assert (interval >= interval_t::zero());
-
-    push_timer_heap(td);
-}
-
-Timer::Timer(Timer&& t)
-: td(t.td)
-{
-    t.td = nullptr;
-    if (td)
-    {
-        assert (td->owner == &t);
-        td->owner = this;
-    }
-}
-
-Timer& Timer::operator = (Timer&& t)
-{
-    std::swap(td, t.td);
-    if (td)
-    {
-        assert (td->owner == &t);
-        td->owner = this;
-    }
-    if (t.td)
-    {
-        assert (t.td->owner == this);
-        t.td->owner = &t;
-    }
-    return *this;
-}
-
-interval_t do_timer(tick_t tick)
-{
-    /// Number of milliseconds until it calls this again
-    // this says to wait 1 sec if all timers get popped
-    interval_t nextmin = std::chrono::seconds(1);
-
-    while (dumb_ptr<TimerData> td = top_timer_heap())
-    {
-        // while the heap is not empty and
-        if (td->tick > tick)
-        {
-            /// Return the time until the next timer needs to goes off
-            nextmin = td->tick - tick;
-            break;
-        }
-        pop_timer_heap();
-
-        // Prevent destroying the object we're in.
-        // Note: this would be surprising in an interval timer,
-        // but all interval timers do an immediate explicit detach().
-        if (td->owner)
-            td->owner->detach();
-        // If we are too far past the requested tick, call with
-        // the current tick instead to fix reregistration problems
-        if (td->tick + std::chrono::seconds(1) < tick)
-            td->func(td.operator->(), tick);
-        else
-            td->func(td.operator->(), td->tick);
-
-        if (td->interval == interval_t::zero())
-        {
-            td.delete_();
-            continue;
-        }
-        if (td->tick + std::chrono::seconds(1) < tick)
-            td->tick = tick + td->interval;
-        else
-            td->tick += td->interval;
-        push_timer_heap(td);
-    }
-
-    return std::max(nextmin, std::chrono::milliseconds(10));
-}
-
-tick_t file_modified(ZString name)
-{
-    struct stat buf;
-    if (stat(name.c_str(), &buf))
-        return tick_t();
-    return tick_t(std::chrono::seconds(buf.st_mtime));
-}
-
-bool has_timers()
-{
-    return !timer_heap.empty();
-}
diff --git a/src/common/timer.hpp b/src/common/timer.hpp
deleted file mode 100644
index 7e187a3..0000000
--- a/src/common/timer.hpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef TIMER_HPP
-#define TIMER_HPP
-
-# include "timer.t.hpp"
-
-# include "../sanity.hpp"
-
-# include "../strings/fwd.hpp"
-
-// updated automatically when using milli_clock::now()
-// which is done only by core.cpp
-extern tick_t gettick_cache;
-
-inline
-tick_t gettick(void)
-{
-    return gettick_cache;
-}
-
-/// Do all timers scheduled before tick, and return the number of
-/// milliseconds until the next timer happens
-interval_t do_timer(tick_t tick);
-
-/// Stat a file, and return its modification time, truncated to seconds.
-tick_t file_modified(ZString name);
-
-/// Check if there are any events at all scheduled.
-bool has_timers();
-
-#endif // TIMER_HPP
diff --git a/src/common/timer.t.hpp b/src/common/timer.t.hpp
deleted file mode 100644
index 1e3a87a..0000000
--- a/src/common/timer.t.hpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#ifndef TIMER_T_HPP
-#define TIMER_T_HPP
-
-# include <chrono>
-# include <functional>
-
-# include "dumb_ptr.hpp"
-
-struct TimerData;
-
-/// An implementation of the C++ "clock" concept, exposing
-/// durations in milliseconds.
-class milli_clock
-{
-public:
-    typedef std::chrono::milliseconds duration;
-    typedef duration::rep rep;
-    typedef duration::period period;
-    typedef std::chrono::time_point<milli_clock, duration> time_point;
-    static const bool is_steady = true; // assumed - not necessarily true
-
-    static time_point now() noexcept;
-};
-
-/// A point in time.
-typedef milli_clock::time_point tick_t;
-/// The difference between two points in time.
-typedef milli_clock::duration interval_t;
-/// (to get additional arguments, use std::bind or a lambda).
-typedef std::function<void (TimerData *, tick_t)> timer_func;
-
-class Timer
-{
-    friend struct TimerData;
-    dumb_ptr<TimerData> td;
-
-    Timer(const Timer&) = delete;
-    Timer& operator = (const Timer&) = delete;
-public:
-    /// Don't own anything yet.
-    Timer() = default;
-    /// Schedule a timer for the given tick.
-    /// If you do not wish to keep track of it, call disconnect().
-    /// Otherwise, you may cancel() or replace (operator =) it later.
-    ///
-    /// If the interval argument is given, the timer will reschedule
-    /// itself again forever. Otherwise, it will disconnect() itself
-    /// just BEFORE it is called.
-    Timer(tick_t tick, timer_func func, interval_t interval=interval_t::zero());
-
-    Timer(Timer&& t);
-    Timer& operator = (Timer&& t);
-    ~Timer() { cancel(); }
-
-    /// Cancel the delivery of this timer's function, and make it falsy.
-    /// Implementation note: this doesn't actually remove it, just sets
-    /// the functor to do_nothing, and waits for the tick before removing.
-    void cancel();
-    /// Make it falsy without cancelling the timer,
-    void detach();
-
-    /// Check if there is a timer connected.
-    explicit operator bool() { return bool(td); }
-    /// Check if there is no connected timer.
-    bool operator !() { return !td; }
-};
-
-#endif // TIMER_T_HPP
diff --git a/src/common/utils.cpp b/src/common/utils.cpp
deleted file mode 100644
index 0dbf145..0000000
--- a/src/common/utils.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-#include "utils.hpp"
-
-#include <netinet/in.h>
-#include <sys/time.h>
-
-#include <algorithm>
-
-#include "../strings/astring.hpp"
-#include "../strings/zstring.hpp"
-#include "../strings/xstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-#include "../io/write.hpp"
-
-#include "extract.hpp"
-
-#include "../poison.hpp"
-
-//---------------------------------------------------
-// E-mail check: return 0 (not correct) or 1 (valid).
-//---------------------------------------------------
-bool e_mail_check(XString email)
-{
-    // athena limits
-    if (email.size() < 3 || email.size() > 39)
-        return 0;
-
-    // part of RFC limits (official reference of e-mail description)
-    XString::iterator at = std::find(email.begin(), email.end(), '@');
-    if (at == email.end())
-        return 0;
-    XString username = email.xislice_h(at);
-    XString hostname = email.xislice_t(at + 1);
-    if (!username || !hostname)
-        return 0;
-    if (hostname.contains('@'))
-        return 0;
-    if (hostname.front() == '.' || hostname.back() == '.')
-        return 0;
-    if (hostname.contains_seq(".."))
-        return 0;
-    if (email.contains_any(" ;"))
-        return 0;
-    return email.is_print();
-}
-
-//-------------------------------------------------
-// Return numerical value of a switch configuration
-// on/off, english, français, deutsch, español
-//-------------------------------------------------
-int config_switch(ZString str)
-{
-    if (str == "true" || str == "on" || str == "yes"
-        || str == "oui" || str == "ja"
-        || str == "si")
-        return 1;
-    if (str == "false" || str == "off" || str == "no"
-        || str == "non" || str == "nein")
-        return 0;
-
-    int rv;
-    if (extract(str, &rv))
-        return rv;
-    FPRINTF(stderr, "Fatal: bad option value %s", str);
-    abort();
-}
-
-static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer");
-static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer");
-
-void stamp_time(timestamp_seconds_buffer& out, const TimeT *t)
-{
-    struct tm when = t ? *t : TimeT::now();
-    char buf[20];
-    strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
-    out = stringish<timestamp_seconds_buffer>(const_(buf));
-}
-void stamp_time(timestamp_milliseconds_buffer& out)
-{
-    struct timeval tv;
-    gettimeofday(&tv, NULL);
-    struct tm when = TimeT(tv.tv_sec);
-    char buf[24];
-    strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
-    sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000));
-    out = stringish<timestamp_milliseconds_buffer>(const_(buf));
-}
-
-void log_with_timestamp(io::WriteFile& out, XString line)
-{
-    if (!line)
-    {
-        out.put('\n');
-        return;
-    }
-    timestamp_milliseconds_buffer tmpstr;
-    stamp_time(tmpstr);
-    out.really_put(tmpstr.data(), tmpstr.size());
-    out.really_put(": ", 2);
-    out.put_line(line);
-}
diff --git a/src/common/utils.hpp b/src/common/utils.hpp
deleted file mode 100644
index 161cbd4..0000000
--- a/src/common/utils.hpp
+++ /dev/null
@@ -1,140 +0,0 @@
-#ifndef UTILS_HPP
-#define UTILS_HPP
-
-# include "../sanity.hpp"
-
-# include <cstdio>
-# include <cstring>
-
-# include <type_traits>
-
-# include "../strings/fwd.hpp"
-# include "../strings/vstring.hpp"
-
-# include "../io/fwd.hpp"
-
-# include "const_array.hpp"
-# include "operators.hpp"
-# include "utils2.hpp"
-
-template<class T>
-struct is_trivially_copyable
-: std::integral_constant<bool,
-    // come back when GCC actually implements the public traits properly
-    __has_trivial_copy(T)
-    && __has_trivial_assign(T)
-    && __has_trivial_destructor(T)>
-{};
-
-bool e_mail_check(XString email);
-int config_switch (ZString str);
-
-inline
-void really_memcpy(uint8_t *dest, const uint8_t *src, size_t n)
-{
-    memcpy(dest, src, n);
-}
-
-inline
-void really_memmove(uint8_t *dest, const uint8_t *src, size_t n)
-{
-    memmove(dest, src, n);
-}
-inline
-bool really_memequal(const uint8_t *a, const uint8_t *b, size_t n)
-{
-    return memcmp(a, b, n) == 0;
-}
-
-inline
-void really_memset0(uint8_t *dest, size_t n)
-{
-    memset(dest, '\0', n);
-}
-template<class T>
-void really_memzero_this(T *v)
-{
-    static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types");
-    static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)");
-    memset(v, '\0', sizeof(*v));
-}
-template<class T, size_t n>
-void really_memzero_this(T (&)[n]) = delete;
-
-// Exists in place of time_t, to give it a predictable printf-format.
-// (on x86 and amd64, time_t == long, but not on x32)
-static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t");
-struct TimeT : Comparable
-{
-    long long value;
-
-    // conversion
-    TimeT(time_t t=0) : value(t) {}
-    TimeT(struct tm t) : value(timegm(&t)) {}
-    operator time_t() const { return value; }
-    operator struct tm() const { time_t v = value; return *gmtime(&v); }
-
-    explicit operator bool() const { return value; }
-    bool operator !() const { return !value; }
-
-    // prevent surprises
-    template<class T>
-    TimeT(T) = delete;
-    template<class T>
-    operator T() const = delete;
-
-    static
-    TimeT now()
-    {
-        // poisoned, but this is still in header-land
-        return time(NULL);
-    }
-
-    bool error() const
-    {
-        return value == -1;
-    }
-    bool okay() const
-    {
-        return !error();
-    }
-};
-
-inline
-long long convert_for_printf(TimeT t)
-{
-    return t.value;
-}
-
-inline
-long long& convert_for_scanf(TimeT& t)
-{
-    return t.value;
-}
-
-struct timestamp_seconds_buffer : VString<19> {};
-struct timestamp_milliseconds_buffer : VString<23> {};
-void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr);
-void stamp_time(timestamp_milliseconds_buffer&);
-
-void log_with_timestamp(io::WriteFile& out, XString line);
-
-// TODO VString?
-# define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS"
-static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer),
-        "timestamp size");
-# define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY
-//  str:            prefix: YYYY-MM-DD HH:MM:SS
-//  sizeof:        01234567890123456789012345678
-//  str + sizeof:                               ^
-//  -1:                     ^
-// there's probably a better way to do this now
-# define REPLACE_TIMESTAMP(str, t)                          \
-    stamp_time(                                             \
-            reinterpret_cast<timestamp_seconds_buffer *>(   \
-                str + sizeof(str)                           \
-            )[-1],                                          \
-            &t                                              \
-    )
-
-#endif //UTILS_HPP
diff --git a/src/common/utils2.hpp b/src/common/utils2.hpp
deleted file mode 100644
index 9af39e5..0000000
--- a/src/common/utils2.hpp
+++ /dev/null
@@ -1,256 +0,0 @@
-#ifndef UTILS2_HPP
-#define UTILS2_HPP
-
-# include "../sanity.hpp"
-
-# include <algorithm>
-# include <functional>
-# include <iterator>
-# include <memory>
-# include <type_traits>
-
-# include "iter.hpp"
-
-# ifdef __clang__
-#  define FALLTHROUGH [[clang::fallthrough]]
-# else
-#  define FALLTHROUGH /* fallthrough */
-# endif
-
-template<class T, class E, E max>
-struct earray
-{
-    // no ctor/dtor and one public member variable for easy initialization
-    T _data[size_t(max)];
-
-    T& operator[](E v)
-    {
-        return _data[size_t(v)];
-    }
-
-    const T& operator[](E v) const
-    {
-        return _data[size_t(v)];
-    }
-
-    T *begin()
-    {
-        return _data;
-    }
-
-    T *end()
-    {
-        return _data + size_t(max);
-    }
-
-    const T *begin() const
-    {
-        return _data;
-    }
-
-    const T *end() const
-    {
-        return _data + size_t(max);
-    }
-
-    friend bool operator == (const earray& l, const earray& r)
-    {
-        return std::equal(l.begin(), l.end(), r.begin());
-    }
-
-    friend bool operator != (const earray& l, const earray& r)
-    {
-        return !(l == r);
-    }
-};
-
-template<class T, class E>
-class eptr
-{
-    T *_data;
-public:
-    eptr(decltype(nullptr)=nullptr)
-    : _data(nullptr)
-    {}
-
-    template<E max>
-    eptr(earray<T, E, max>& arr)
-    : _data(arr._data)
-    {}
-
-    T& operator [](E v)
-    {
-        return _data[size_t(v)];
-    }
-
-    explicit operator bool()
-    {
-        return _data;
-    }
-
-    bool operator not()
-    {
-        return not _data;
-    }
-};
-
-// std::underlying_type isn't supported until gcc 4.7
-// this is a poor man's emulation
-template<class E>
-struct underlying_type
-{
-    static_assert(std::is_enum<E>::value, "Only enums have underlying type!");
-    typedef typename std::conditional<
-        std::is_signed<E>::value,
-        typename std::make_signed<E>::type,
-        typename std::make_unsigned<E>::type
-    >::type type;
-};
-
-template<class E, bool=std::is_enum<E>::value>
-struct remove_enum
-{
-    typedef E type;
-};
-template<class E>
-struct remove_enum<E, true>
-{
-    typedef typename underlying_type<E>::type type;
-};
-
-
-# define ENUM_BITWISE_OPERATORS(E)      \
-inline                                  \
-E operator & (E l, E r)                 \
-{                                       \
-    typedef underlying_type<E>::type U; \
-    return E(U(l) & U(r));              \
-}                                       \
-inline                                  \
-E operator | (E l, E r)                 \
-{                                       \
-    typedef underlying_type<E>::type U; \
-    return E(U(l) | U(r));              \
-}                                       \
-inline                                  \
-E operator ^ (E l, E r)                 \
-{                                       \
-    typedef underlying_type<E>::type U; \
-    return E(U(l) ^ U(r));              \
-}                                       \
-inline                                  \
-E& operator &= (E& l, E r)              \
-{                                       \
-    return l = l & r;                   \
-}                                       \
-inline                                  \
-E& operator |= (E& l, E r)              \
-{                                       \
-    return l = l | r;                   \
-}                                       \
-inline                                  \
-E& operator ^= (E& l, E r)              \
-{                                       \
-    return l = l ^ r;                   \
-}                                       \
-inline                                  \
-E operator ~ (E r)                      \
-{                                       \
-    return E(-1) ^ r;                   \
-}
-
-template<class E>
-class EnumMath
-{
-    typedef typename underlying_type<E>::type U;
-public:
-    static
-    E inced(E v)
-    {
-        return E(U(v) + 1);
-    }
-};
-
-template<class E>
-IteratorPair<ValueIterator<E, EnumMath<E>>> erange(E b, E e)
-{
-    return {b, e};
-}
-
-namespace ph = std::placeholders;
-
-template<class A, class B>
-typename std::common_type<A, B>::type min(A a, B b)
-{
-    return a < b ? a : b;
-}
-
-template<class A, class B>
-typename std::common_type<A, B>::type max(A a, B b)
-{
-    return b < a ? a : b;
-}
-
-template<class T>
-struct is_array_of_unknown_bound
-: std::is_same<T, typename std::remove_extent<T>::type[]>
-{};
-
-template<class T, class D=std::default_delete<T>, class... A>
-typename std::enable_if<!is_array_of_unknown_bound<T>::value, std::unique_ptr<T, D>>::type make_unique(A&&... a)
-{
-    return std::unique_ptr<T, D>(new T(std::forward<A>(a)...));
-}
-
-template<class T, class D=std::default_delete<T>>
-typename std::enable_if<is_array_of_unknown_bound<T>::value, std::unique_ptr<T, D>>::type make_unique(size_t sz)
-{
-    typedef typename std::remove_extent<T>::type E;
-    return std::unique_ptr<E[], D>(new E[sz]());
-}
-
-template<class T>
-const T& const_(T& t)
-{
-    return t;
-}
-
-template<class T, class U>
-T no_cast(U&& u)
-{
-    typedef typename std::remove_reference<T>::type Ti;
-    typedef typename std::remove_reference<U>::type Ui;
-    typedef typename std::remove_cv<Ti>::type Tb;
-    typedef typename std::remove_cv<Ui>::type Ub;
-    static_assert(std::is_same<Tb, Ub>::value, "not no cast");
-    return std::forward<U>(u);
-}
-
-template<class T, class U>
-T base_cast(U&& u)
-{
-    static_assert(std::is_reference<T>::value, "must base cast with references");
-    typedef typename std::remove_reference<T>::type Ti;
-    typedef typename std::remove_reference<U>::type Ui;
-    typedef typename std::remove_cv<Ti>::type Tb;
-    typedef typename std::remove_cv<Ui>::type Ub;
-    static_assert(std::is_base_of<Tb, Ub>::value, "not base cast");
-    return std::forward<U>(u);
-}
-
-// use this when e.g. U is an int of unknown size
-template<class T, class U>
-T maybe_cast(U u)
-{
-    return u;
-}
-
-template<class T, class U>
-typename std::remove_pointer<T>::type *sign_cast(U *u)
-{
-    typedef typename std::remove_pointer<T>::type T_;
-    static_assert(sizeof(T_) == sizeof(U), "sign cast must be same size");
-    return reinterpret_cast<T_ *>(u);
-}
-
-#endif // UTILS2_HPP
diff --git a/src/common/version.cpp b/src/common/version.cpp
deleted file mode 100644
index 811ffdf..0000000
--- a/src/common/version.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "version.hpp"
-
-#include "../conf/version.hpp"
-
-#include "../strings/xstring.hpp"
-
-#include "extract.hpp"
-
-Version CURRENT_VERSION =
-{
-    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
-    VERSION_DEVEL,
-
-    0, 0,
-    VENDOR_VERSION,
-};
-Version CURRENT_LOGIN_SERVER_VERSION =
-{
-    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
-    VERSION_DEVEL,
-
-    0, TMWA_SERVER_LOGIN,
-    VENDOR_VERSION,
-};
-Version CURRENT_CHAR_SERVER_VERSION =
-{
-    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
-    VERSION_DEVEL,
-
-    0, TMWA_SERVER_CHAR | TMWA_SERVER_INTER,
-    VENDOR_VERSION,
-};
-Version CURRENT_MAP_SERVER_VERSION =
-{
-    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
-    VERSION_DEVEL,
-
-    0, TMWA_SERVER_MAP,
-    VENDOR_VERSION,
-};
-
-#define S2(a) #a
-#define S(a) S2(a)
-
-const char CURRENT_VERSION_STRING[] = "TMWA "
-        S(VERSION_MAJOR) "." S(VERSION_MINOR) "." S(VERSION_PATCH)
-        " dev" S(VERSION_DEVEL) " (" VENDOR " " S(VENDOR_VERSION) ")";
-
-bool extract(XString str, Version *vers)
-{
-    *vers = {};
-    return extract(str, record<'.'>(&vers->major, &vers->minor, &vers->patch));
-}
diff --git a/src/common/version.hpp b/src/common/version.hpp
deleted file mode 100644
index a2c4e05..0000000
--- a/src/common/version.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef TMWA_COMMON_VERSION_HPP
-#define TMWA_COMMON_VERSION_HPP
-
-# include <cstdint>
-
-# include "../strings/fwd.hpp"
-
-// TODO make these bitwise enums
-# define TMWA_FLAG_REGISTRATION 0x01
-
-# define TMWA_SERVER_LOGIN      0x01
-# define TMWA_SERVER_CHAR       0x02
-# define TMWA_SERVER_INTER      0x04
-# define TMWA_SERVER_MAP        0x08
-
-struct Version
-{
-    uint8_t major;
-    uint8_t minor; // flavor1
-    uint8_t patch; // flavor2
-    uint8_t devel; // flavor3
-
-    uint8_t flags;
-    uint8_t which;
-    uint16_t vend;
-    // can't add vendor name yet
-};
-static_assert(sizeof(Version) == 8, "this is sent over the network, can't change");
-
-extern Version CURRENT_VERSION;
-
-extern Version CURRENT_LOGIN_SERVER_VERSION;
-extern Version CURRENT_CHAR_SERVER_VERSION;
-extern Version CURRENT_MAP_SERVER_VERSION;
-
-extern const char CURRENT_VERSION_STRING[];
-
-bool extract(XString str, Version *vers);
-
-constexpr
-bool operator < (Version l, Version r)
-{
-    return (l.major < r.major
-            || (l.major == r.major
-                && (l.minor < r.minor
-                    || (l.minor == r.minor
-                        && (l.patch < r.patch
-                            || (l.patch == r.patch
-                                && (l.devel < r.devel
-                                    || (l.devel == r.devel
-                                        && l.vend < r.vend))))))));
-}
-constexpr
-bool operator > (Version l, Version r)
-{
-    return r < l;
-}
-constexpr
-bool operator <= (Version l, Version r)
-{
-    return !(r < l);
-}
-constexpr
-bool operator >= (Version l, Version r)
-{
-    return !(l < r);
-}
-
-#endif // TMWA_COMMON_VERSION_HPP
diff --git a/src/compat/alg.cpp b/src/compat/alg.cpp
new file mode 100644
index 0000000..7800e79
--- /dev/null
+++ b/src/compat/alg.cpp
@@ -0,0 +1,3 @@
+#include "alg.hpp"
+
+#include "../poison.hpp"
diff --git a/src/compat/alg.hpp b/src/compat/alg.hpp
new file mode 100644
index 0000000..250c161
--- /dev/null
+++ b/src/compat/alg.hpp
@@ -0,0 +1,21 @@
+#ifndef TMWA_COMPAT_ALG_HPP
+#define TMWA_COMPAT_ALG_HPP
+
+# include "../sanity.hpp"
+
+# include <type_traits>
+
+
+template<class A, class B>
+typename std::common_type<A, B>::type min(A a, B b)
+{
+    return a < b ? a : b;
+}
+
+template<class A, class B>
+typename std::common_type<A, B>::type max(A a, B b)
+{
+    return b < a ? a : b;
+}
+
+#endif // TMWA_COMPAT_ALG_HPP
diff --git a/src/compat/attr.hpp b/src/compat/attr.hpp
new file mode 100644
index 0000000..ca9a7a2
--- /dev/null
+++ b/src/compat/attr.hpp
@@ -0,0 +1,13 @@
+#ifndef TMWA_COMPAT_ATTR_HPP
+#define TMWA_COMPAT_ATTR_HPP
+
+# include "../sanity.hpp"
+
+
+# ifdef __clang__
+#  define FALLTHROUGH [[clang::fallthrough]]
+# else
+#  define FALLTHROUGH /* fallthrough */
+# endif
+
+#endif // TMWA_COMPAT_ATTR_HPP
diff --git a/src/compat/cast.cpp b/src/compat/cast.cpp
new file mode 100644
index 0000000..015fd27
--- /dev/null
+++ b/src/compat/cast.cpp
@@ -0,0 +1,3 @@
+#include "cast.hpp"
+
+#include "../poison.hpp"
diff --git a/src/compat/cast.hpp b/src/compat/cast.hpp
new file mode 100644
index 0000000..c1b9bab
--- /dev/null
+++ b/src/compat/cast.hpp
@@ -0,0 +1,54 @@
+#ifndef TMWA_COMPAT_CAST_HPP
+#define TMWA_COMPAT_CAST_HPP
+
+# include "../sanity.hpp"
+
+# include <utility>
+# include <type_traits>
+
+
+template<class T>
+const T& const_(T& t)
+{
+    return t;
+}
+
+template<class T, class U>
+T no_cast(U&& u)
+{
+    typedef typename std::remove_reference<T>::type Ti;
+    typedef typename std::remove_reference<U>::type Ui;
+    typedef typename std::remove_cv<Ti>::type Tb;
+    typedef typename std::remove_cv<Ui>::type Ub;
+    static_assert(std::is_same<Tb, Ub>::value, "not no cast");
+    return std::forward<U>(u);
+}
+
+template<class T, class U>
+T base_cast(U&& u)
+{
+    static_assert(std::is_reference<T>::value, "must base cast with references");
+    typedef typename std::remove_reference<T>::type Ti;
+    typedef typename std::remove_reference<U>::type Ui;
+    typedef typename std::remove_cv<Ti>::type Tb;
+    typedef typename std::remove_cv<Ui>::type Ub;
+    static_assert(std::is_base_of<Tb, Ub>::value, "not base cast");
+    return std::forward<U>(u);
+}
+
+// use this when e.g. U is an int of unknown size
+template<class T, class U>
+T maybe_cast(U u)
+{
+    return u;
+}
+
+template<class T, class U>
+typename std::remove_pointer<T>::type *sign_cast(U *u)
+{
+    typedef typename std::remove_pointer<T>::type T_;
+    static_assert(sizeof(T_) == sizeof(U), "sign cast must be same size");
+    return reinterpret_cast<T_ *>(u);
+}
+
+#endif // TMWA_COMPAT_CAST_HPP
diff --git a/src/compat/fun.hpp b/src/compat/fun.hpp
new file mode 100644
index 0000000..536a113
--- /dev/null
+++ b/src/compat/fun.hpp
@@ -0,0 +1,11 @@
+#ifndef TMWA_COMPAT_FUN_HPP
+#define TMWA_COMPAT_FUN_HPP
+
+# include "../sanity.hpp"
+
+# include <functional>
+
+
+namespace ph = std::placeholders;
+
+#endif // TMWA_COMPAT_FUN_HPP
diff --git a/src/compat/iter.cpp b/src/compat/iter.cpp
new file mode 100644
index 0000000..2b1fb0c
--- /dev/null
+++ b/src/compat/iter.cpp
@@ -0,0 +1,3 @@
+#include "iter.hpp"
+
+#include "../poison.hpp"
diff --git a/src/compat/iter.hpp b/src/compat/iter.hpp
new file mode 100644
index 0000000..7793d90
--- /dev/null
+++ b/src/compat/iter.hpp
@@ -0,0 +1,97 @@
+#ifndef TMWA_COMPAT_ITER_HPP
+#define TMWA_COMPAT_ITER_HPP
+//    iter.hpp - tools for dealing with iterators
+//
+//    Copyright © 2012-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include <iterator>
+
+
+/// Simple class to use a pair of iterators with foreach
+template<class It>
+class IteratorPair
+{
+    It _b, _e;
+public:
+    IteratorPair(It b, It e)
+    : _b(b), _e(e)
+    {}
+
+    It begin() { return _b; }
+    It end() { return _e; }
+};
+
+template<class It>
+IteratorPair<It> iterator_pair(It b, It e)
+{
+    return {b, e};
+}
+
+template<class T>
+class PassthroughMath
+{
+public:
+    static
+    T inced(T v) { return ++v; }
+};
+
+// An iterator that just directly contains an integer-like value
+// TODO port this once the new integer API happens
+template<class T, class Math=PassthroughMath<T>>
+class ValueIterator
+{
+    T value;
+public:
+    typedef std::forward_iterator_tag iterator_category;
+    typedef void difference_type;
+    typedef T value_type;
+    typedef void reference;
+    typedef void pointer;
+public:
+    ValueIterator(T v)
+    : value(v)
+    {}
+
+    T operator *()
+    {
+        return value;
+    }
+    ValueIterator& operator++ ()
+    {
+        value = Math::inced(value);
+        return *this;
+    }
+    friend bool operator == (ValueIterator l, ValueIterator r)
+    {
+        return l.value == r.value;
+    }
+    friend bool operator != (ValueIterator l, ValueIterator r)
+    {
+        return !(l == r);
+    }
+};
+
+template<class T>
+IteratorPair<ValueIterator<T>> value_range(T b, T e)
+{
+    return {b, e};
+}
+
+#endif // TMWA_COMPAT_ITER_HPP
diff --git a/src/compat/iter_test.cpp b/src/compat/iter_test.cpp
new file mode 100644
index 0000000..647ebf9
--- /dev/null
+++ b/src/compat/iter_test.cpp
@@ -0,0 +1,82 @@
+#include "iter.hpp"
+
+#include <gtest/gtest.h>
+
+#include "../strings/xstring.hpp"
+
+TEST(iterpair, string)
+{
+    IteratorPair<ValueIterator<char>> pair = value_range('0', ':');
+    const char *str = "0123456789";
+    EXPECT_TRUE(std::equal(pair.begin(), pair.end(), str));
+}
+
+TEST(iterpair, signed8)
+{
+    IteratorPair<ValueIterator<int8_t>> pair = value_range(int8_t(-128), int8_t(127));
+    int8_t arr[255] =
+    {
+        -128, -127, -126, -125, -124, -123, -122, -121, -120,
+        -119, -118, -117, -116, -115, -114, -113, -112, -111, -110,
+        -109, -108, -107, -106, -105, -104, -103, -102, -101, -100,
+        -99, -98, -97, -96, -95, -94, -93, -92, -91, -90,
+        -89, -88, -87, -86, -85, -84, -83, -82, -81, -80,
+        -79, -78, -77, -76, -75, -74, -73, -72, -71, -70,
+        -69, -68, -67, -66, -65, -64, -63, -62, -61, -60,
+        -59, -58, -57, -56, -55, -54, -53, -52, -51, -50,
+        -49, -48, -47, -46, -45, -44, -43, -42, -41, -40,
+        -39, -38, -37, -36, -35, -34, -33, -32, -31, -30,
+        -29, -28, -27, -26, -25, -24, -23, -22, -21, -20,
+        -19, -18, -17, -16, -15, -14, -13, -12, -11, -10,
+        -9, -8, -7, -6, -5, -4, -3, -2, -1,
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+        10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+        20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+        30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+        40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+        50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+        60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+        70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+        80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
+        90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
+        100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+        110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+        120, 121, 122, 123, 124, 125, 126,
+    };
+    EXPECT_TRUE(std::equal(pair.begin(), pair.end(), arr + 0));
+}
+
+TEST(iterpair, unsigned8)
+{
+    IteratorPair<ValueIterator<uint8_t>> pair = value_range(uint8_t(0), uint8_t(255));
+    uint8_t arr[255] =
+    {
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+        10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+        20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+        30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+        40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+        50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+        60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+        70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+        80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
+        90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
+        100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+        110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+        120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
+        130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
+        140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+        150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+        160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
+        170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+        180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
+        190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
+        200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+        210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+        220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
+        230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+        240, 241, 242, 243, 244, 245, 246, 247, 248, 249,
+        250, 251, 252, 253, 254,
+    };
+    EXPECT_TRUE(std::equal(pair.begin(), pair.end(), arr));
+}
diff --git a/src/compat/memory.cpp b/src/compat/memory.cpp
new file mode 100644
index 0000000..6a5f526
--- /dev/null
+++ b/src/compat/memory.cpp
@@ -0,0 +1,3 @@
+#include "memory.hpp"
+
+#include "../poison.hpp"
diff --git a/src/compat/memory.hpp b/src/compat/memory.hpp
new file mode 100644
index 0000000..2839640
--- /dev/null
+++ b/src/compat/memory.hpp
@@ -0,0 +1,27 @@
+#ifndef TMWA_COMPAT_MEMORY_HPP
+#define TMWA_COMPAT_MEMORY_HPP
+
+# include "../sanity.hpp"
+
+# include <memory>
+
+
+template<class T>
+struct is_array_of_unknown_bound
+: std::is_same<T, typename std::remove_extent<T>::type[]>
+{};
+
+template<class T, class D=std::default_delete<T>, class... A>
+typename std::enable_if<!is_array_of_unknown_bound<T>::value, std::unique_ptr<T, D>>::type make_unique(A&&... a)
+{
+    return std::unique_ptr<T, D>(new T(std::forward<A>(a)...));
+}
+
+template<class T, class D=std::default_delete<T>>
+typename std::enable_if<is_array_of_unknown_bound<T>::value, std::unique_ptr<T, D>>::type make_unique(size_t sz)
+{
+    typedef typename std::remove_extent<T>::type E;
+    return std::unique_ptr<E[], D>(new E[sz]());
+}
+
+#endif // TMWA_COMPAT_MEMORY_HPP
diff --git a/src/compat/nullpo.cpp b/src/compat/nullpo.cpp
new file mode 100644
index 0000000..423ed8c
--- /dev/null
+++ b/src/compat/nullpo.cpp
@@ -0,0 +1,28 @@
+#include "nullpo.hpp"
+
+#include <cstdio>
+
+#include "../poison.hpp"
+
+/// Actual output function
+static
+void nullpo_info(const char *file, int line, const char *func)
+{
+    if (!file)
+        file = "??";
+    if (!func || !*func)
+        func = "unknown";
+
+    fprintf(stderr, "%s:%d: in func `%s': NULL pointer\n",
+            file, line, func);
+}
+
+bool nullpo_chk(const char *file, int line, const char *func,
+                 const void *target)
+{
+    if (target)
+        return 0;
+
+    nullpo_info(file, line, func);
+    return 1;
+}
diff --git a/src/compat/nullpo.hpp b/src/compat/nullpo.hpp
new file mode 100644
index 0000000..6eca4b6
--- /dev/null
+++ b/src/compat/nullpo.hpp
@@ -0,0 +1,41 @@
+/// return wrappers for unexpected NULL pointers
+#ifndef TMWA_COMPAT_NULLPO_HPP
+#define TMWA_COMPAT_NULLPO_HPP
+/// Uncomment this to live dangerously
+/// (really exist to detect mostly-unused variables)
+//# define BUG_FREE
+
+/// All functions print to standard error (was: standard output)
+/// nullpo_ret(cond) - return 0 if given pointer is NULL
+/// nullpo_retv(cond) - just return (function returns void)
+/// nullpo_retr(rv, cond) - return given value instead
+
+# ifndef BUG_FREE
+#  define nullpo_retr(ret, t)                                   \
+    if (nullpo_chk(__FILE__, __LINE__, __PRETTY_FUNCTION__, t)) \
+        return ret;
+# else // BUG_FREE
+#  define nullpo_retr(ret, t) /*t*/
+# endif // BUG_FREE
+
+# define nullpo_ret(t) nullpo_retr(0, t)
+# define nullpo_retv(t) nullpo_retr(, t)
+
+# include "../sanity.hpp"
+
+/// Used by macros in this header
+bool nullpo_chk(const char *file, int line, const char *func,
+        const void *target);
+
+template<class T>
+bool nullpo_chk(const char *file, int line, const char *func, T target)
+{
+    return nullpo_chk(file, line, func, target.operator->());
+}
+template<class T>
+bool nullpo_chk(const char *file, int line, const char *func, T *target)
+{
+    return nullpo_chk(file, line, func, static_cast<const void *>(target));
+}
+
+#endif // TMWA_COMPAT_NULLPO_HPP
diff --git a/src/compat/rawmem.cpp b/src/compat/rawmem.cpp
new file mode 100644
index 0000000..9e353e8
--- /dev/null
+++ b/src/compat/rawmem.cpp
@@ -0,0 +1,3 @@
+#include "rawmem.hpp"
+
+#include "../poison.hpp"
diff --git a/src/compat/rawmem.hpp b/src/compat/rawmem.hpp
new file mode 100644
index 0000000..ac08964
--- /dev/null
+++ b/src/compat/rawmem.hpp
@@ -0,0 +1,30 @@
+#ifndef TMWA_COMPAT_RAWMEM_HPP
+#define TMWA_COMPAT_RAWMEM_HPP
+
+# include <cstddef>
+# include <cstdint>
+# include <cstring>
+
+inline
+void really_memcpy(uint8_t *dest, const uint8_t *src, size_t n)
+{
+    memcpy(dest, src, n);
+}
+
+inline
+void really_memmove(uint8_t *dest, const uint8_t *src, size_t n)
+{
+    memmove(dest, src, n);
+}
+inline
+bool really_memequal(const uint8_t *a, const uint8_t *b, size_t n)
+{
+    return memcmp(a, b, n) == 0;
+}
+
+inline
+void really_memset0(uint8_t *dest, size_t n)
+{
+    memset(dest, '\0', n);
+}
+#endif // TMWA_COMPAT_RAWMEM_HPP
diff --git a/src/conf/version.hpp b/src/conf/version.hpp
index a4ee931..ab7a3d5 100644
--- a/src/conf/version.hpp
+++ b/src/conf/version.hpp
@@ -1,5 +1,5 @@
-#ifndef CONF_VERSION_HPP
-#define CONF_VERSION_HPP
+#ifndef TMWA_CONF_VERSION_HPP
+#define TMWA_CONF_VERSION_HPP
 
 # include "conf-raw/str-VERSION_FULL.h"
 # include "conf-raw/str-VERSION_HASH.h"
@@ -12,4 +12,4 @@
 # include "conf-raw/str-VENDOR.h"
 # include "conf-raw/int-VENDOR_VERSION.h"
 
-#endif
+#endif // TMWA_CONF_VERSION_HPP
diff --git a/src/generic/const_array.cpp b/src/generic/const_array.cpp
new file mode 100644
index 0000000..0c09333
--- /dev/null
+++ b/src/generic/const_array.cpp
@@ -0,0 +1,3 @@
+#include "const_array.hpp"
+
+#include "../poison.hpp"
diff --git a/src/generic/const_array.hpp b/src/generic/const_array.hpp
new file mode 100644
index 0000000..1c70f5d
--- /dev/null
+++ b/src/generic/const_array.hpp
@@ -0,0 +1,132 @@
+#ifndef TMWA_GENERIC_CONST_ARRAY_HPP
+#define TMWA_GENERIC_CONST_ARRAY_HPP
+//    const_array.hpp - just a pointer-to-const and a length
+//
+//    Copyright © 2011-2012 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include <cstring>
+
+# include <iterator>
+# include <ostream>
+# include <vector>
+
+# ifdef WORKAROUND_GCC46_COMPILER
+// constexpr is buggy with templates in this version
+// Is this still needed now that const_string is removed?
+#  define constexpr /* nothing */
+# endif
+
+// TODO see if I ever actually use this, and not the subclass
+template<class T>
+class const_array
+{
+    const T *d;
+    size_t n;
+public:
+    typedef const T *iterator;
+    typedef std::reverse_iterator<iterator> reverse_iterator;
+
+    constexpr
+    const_array(std::nullptr_t)
+    : d(nullptr), n(0)
+    {}
+
+    constexpr
+    const_array(const T *p, size_t z)
+    : d(p), n(z)
+    {}
+
+    constexpr
+    const_array(const T *b, const T *e)
+    : d(b), n(e - b)
+    {}
+
+    const_array(std::initializer_list<T> list)
+    : d(list.begin()), n(list.size())
+    {}
+
+    // Implicit conversion from std::vector
+    const_array(const std::vector<T>& v)
+    : d(v.data()), n(v.size())
+    {}
+
+    // but disallow conversion from a temporary
+    const_array(std::vector<T>&&) = delete;
+
+    // Oops. see src/warnings.hpp
+    constexpr
+    const T *data() const { return d; }
+    constexpr
+    size_t size() const { return n; }
+    constexpr
+    bool empty() const { return not n; }
+    constexpr explicit
+    operator bool() const { return n; }
+
+    constexpr
+    std::pair<const_array, const_array> cut(size_t o) const
+    {
+        return {const_array(d, o), const_array(d + o, n - o)};
+    }
+
+    constexpr
+    const_array first(size_t o) const
+    {
+        return cut(o).first;
+    }
+
+    constexpr
+    const_array last(size_t l) const
+    {
+        return cut(size() - l).second;
+    }
+
+    constexpr
+    const_array after(size_t o) const
+    {
+        return cut(o).second;
+    }
+
+    constexpr
+    iterator begin() const { return d; }
+    constexpr
+    iterator end() const { return d + n; }
+    constexpr
+    reverse_iterator rbegin() const { return reverse_iterator(end()); }
+    constexpr
+    reverse_iterator rend() const { return reverse_iterator(begin()); }
+
+    constexpr
+    const T& front() const { return *begin(); }
+    constexpr
+    const T& back() const { return *rbegin(); }
+
+    // This probably shouldn't be used, but I'm adding it for porting.
+    T& operator[](size_t i)
+    {
+        return const_cast<T&>(d[i]);
+    }
+};
+
+# ifdef WORKAROUND_GCC46_COMPILER
+#  undef constexpr
+# endif
+
+#endif // TMWA_GENERIC_CONST_ARRAY_HPP
diff --git a/src/generic/db.cpp b/src/generic/db.cpp
new file mode 100644
index 0000000..cc58ad8
--- /dev/null
+++ b/src/generic/db.cpp
@@ -0,0 +1,3 @@
+#include "db.hpp"
+
+#include "../poison.hpp"
diff --git a/src/generic/db.hpp b/src/generic/db.hpp
new file mode 100644
index 0000000..314c449
--- /dev/null
+++ b/src/generic/db.hpp
@@ -0,0 +1,180 @@
+#ifndef TMWA_GENERIC_DB_HPP
+#define TMWA_GENERIC_DB_HPP
+//    db.hpp - convenience wrappers over std::map<K, V>
+//
+//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include <map>
+# include <memory>
+
+template<class K, class V>
+class Map
+{
+    typedef std::map<K, V> Impl;
+
+    Impl impl;
+public:
+    Map() = default;
+    Map(std::initializer_list<std::pair<const K, V>> il)
+    : impl(il)
+    {}
+    typedef typename Impl::iterator iterator;
+    typedef typename Impl::const_iterator const_iterator;
+
+    iterator begin() { return impl.begin(); }
+    iterator end() { return impl.end(); }
+    const_iterator begin() const { return impl.begin(); }
+    const_iterator end() const { return impl.end(); }
+
+    V *search(const K& k)
+    {
+        iterator it = impl.find(k);
+        if (it == impl.end())
+            return nullptr;
+        return &it->second;
+    }
+    const V *search(const K& k) const
+    {
+        const_iterator it = impl.find(k);
+        if (it == impl.end())
+            return nullptr;
+        return &it->second;
+    }
+    void insert(const K& k, V v)
+    {
+        // As far as I can tell, this is the simplest way to
+        // implement move-only insert-with-replacement.
+        iterator it = impl.lower_bound(k);
+        // invariant: if it is valid, it->first >= k
+        if (it != impl.end() && it->first == k)
+            it->second = std::move(v);
+        else
+            it = impl.insert(std::pair<K, V>(std::move(k), std::move(v))).first;
+        return (void)&it->second;
+
+    }
+    V *init(const K& k)
+    {
+        return &impl[k];
+    }
+    void erase(const K& k)
+    {
+        impl.erase(k);
+    }
+    void clear()
+    {
+        impl.clear();
+    }
+    bool empty() const
+    {
+        return impl.empty();
+    }
+    size_t size() const
+    {
+        return impl.size();
+    }
+};
+
+template<class K, class V>
+class DMap
+{
+    typedef Map<K, V> Impl;
+
+    Impl impl;
+public:
+    typedef typename Impl::iterator iterator;
+    typedef typename Impl::const_iterator const_iterator;
+
+    iterator begin() { return impl.begin(); }
+    iterator end() { return impl.end(); }
+    const_iterator begin() const { return impl.begin(); }
+    const_iterator end() const { return impl.end(); }
+
+    // const V& ? with a static default V?
+    V get(const K& k)
+    {
+        V *vp = impl.search(k);
+        return vp ? *vp : V();
+    }
+    void put(const K& k, V v)
+    {
+        if (v == V())
+            impl.erase(k);
+        else
+            impl.insert(k, std::move(v));
+    }
+    void clear()
+    {
+        impl.clear();
+    }
+    bool empty() const
+    {
+        return impl.empty();
+    }
+    size_t size() const
+    {
+        return impl.size();
+    }
+};
+
+template<class K, class V>
+class UPMap
+{
+    typedef std::unique_ptr<V> U;
+    typedef Map<K, U> Impl;
+
+    Impl impl;
+public:
+    typedef typename Impl::iterator iterator;
+    typedef typename Impl::const_iterator const_iterator;
+
+    iterator begin() { return impl.begin(); }
+    iterator end() { return impl.end(); }
+    const_iterator begin() const { return impl.begin(); }
+    const_iterator end() const { return impl.end(); }
+
+    // const V& ? with a static default V?
+    V *get(const K& k)
+    {
+        U *up = impl.search(k);
+        return up ? up->get() : nullptr;
+    }
+    void put(const K& k, U v)
+    {
+        if (!v)
+            impl.erase(k);
+        else
+            impl.insert(k, std::move(v));
+    }
+    void clear()
+    {
+        impl.clear();
+    }
+    bool empty() const
+    {
+        return impl.empty();
+    }
+    size_t size() const
+    {
+        return impl.size();
+    }
+};
+
+#endif // TMWA_GENERIC_DB_HPP
diff --git a/src/generic/enum.cpp b/src/generic/enum.cpp
new file mode 100644
index 0000000..c5ab250
--- /dev/null
+++ b/src/generic/enum.cpp
@@ -0,0 +1,3 @@
+#include "enum.hpp"
+
+#include "../poison.hpp"
diff --git a/src/generic/enum.hpp b/src/generic/enum.hpp
new file mode 100644
index 0000000..6f29981
--- /dev/null
+++ b/src/generic/enum.hpp
@@ -0,0 +1,170 @@
+#ifndef TMWA_GENERIC_ENUM_HPP
+#define TMWA_GENERIC_ENUM_HPP
+
+# include "../sanity.hpp"
+
+# include <type_traits>
+
+# include "../compat/iter.hpp"
+
+template<class T, class E, E max>
+struct earray
+{
+    // no ctor/dtor and one public member variable for easy initialization
+    T _data[size_t(max)];
+
+    T& operator[](E v)
+    {
+        return _data[size_t(v)];
+    }
+
+    const T& operator[](E v) const
+    {
+        return _data[size_t(v)];
+    }
+
+    T *begin()
+    {
+        return _data;
+    }
+
+    T *end()
+    {
+        return _data + size_t(max);
+    }
+
+    const T *begin() const
+    {
+        return _data;
+    }
+
+    const T *end() const
+    {
+        return _data + size_t(max);
+    }
+
+    friend bool operator == (const earray& l, const earray& r)
+    {
+        return std::equal(l.begin(), l.end(), r.begin());
+    }
+
+    friend bool operator != (const earray& l, const earray& r)
+    {
+        return !(l == r);
+    }
+};
+
+template<class T, class E>
+class eptr
+{
+    T *_data;
+public:
+    eptr(std::nullptr_t=nullptr)
+    : _data(nullptr)
+    {}
+
+    template<E max>
+    eptr(earray<T, E, max>& arr)
+    : _data(arr._data)
+    {}
+
+    T& operator [](E v)
+    {
+        return _data[size_t(v)];
+    }
+
+    explicit operator bool()
+    {
+        return _data;
+    }
+
+    bool operator not()
+    {
+        return not _data;
+    }
+};
+
+// std::underlying_type isn't supported until gcc 4.7
+// this is a poor man's emulation
+template<class E>
+struct underlying_type
+{
+    static_assert(std::is_enum<E>::value, "Only enums have underlying type!");
+    typedef typename std::conditional<
+        std::is_signed<E>::value,
+        typename std::make_signed<E>::type,
+        typename std::make_unsigned<E>::type
+    >::type type;
+};
+
+template<class E, bool=std::is_enum<E>::value>
+struct remove_enum
+{
+    typedef E type;
+};
+template<class E>
+struct remove_enum<E, true>
+{
+    typedef typename underlying_type<E>::type type;
+};
+
+
+# define ENUM_BITWISE_OPERATORS(E)      \
+inline                                  \
+E operator & (E l, E r)                 \
+{                                       \
+    typedef underlying_type<E>::type U; \
+    return E(U(l) & U(r));              \
+}                                       \
+inline                                  \
+E operator | (E l, E r)                 \
+{                                       \
+    typedef underlying_type<E>::type U; \
+    return E(U(l) | U(r));              \
+}                                       \
+inline                                  \
+E operator ^ (E l, E r)                 \
+{                                       \
+    typedef underlying_type<E>::type U; \
+    return E(U(l) ^ U(r));              \
+}                                       \
+inline                                  \
+E& operator &= (E& l, E r)              \
+{                                       \
+    return l = l & r;                   \
+}                                       \
+inline                                  \
+E& operator |= (E& l, E r)              \
+{                                       \
+    return l = l | r;                   \
+}                                       \
+inline                                  \
+E& operator ^= (E& l, E r)              \
+{                                       \
+    return l = l ^ r;                   \
+}                                       \
+inline                                  \
+E operator ~ (E r)                      \
+{                                       \
+    return E(-1) ^ r;                   \
+}
+
+template<class E>
+class EnumMath
+{
+    typedef typename underlying_type<E>::type U;
+public:
+    static
+    E inced(E v)
+    {
+        return E(U(v) + 1);
+    }
+};
+
+template<class E>
+IteratorPair<ValueIterator<E, EnumMath<E>>> erange(E b, E e)
+{
+    return {b, e};
+}
+
+#endif // TMWA_GENERIC_ENUM_HPP
diff --git a/src/generic/intern-pool.cpp b/src/generic/intern-pool.cpp
new file mode 100644
index 0000000..33649f2
--- /dev/null
+++ b/src/generic/intern-pool.cpp
@@ -0,0 +1,3 @@
+#include "intern-pool.hpp"
+
+#include "../poison.hpp"
diff --git a/src/generic/intern-pool.hpp b/src/generic/intern-pool.hpp
new file mode 100644
index 0000000..69f20ef
--- /dev/null
+++ b/src/generic/intern-pool.hpp
@@ -0,0 +1,41 @@
+#ifndef TMWA_GENERIC_INTERN_POOL_HPP
+#define TMWA_GENERIC_INTERN_POOL_HPP
+
+# include <cassert>
+
+# include <map>
+# include <vector>
+
+# include "../strings/rstring.hpp"
+# include "../strings/zstring.hpp"
+# include "../strings/xstring.hpp"
+
+class InternPool
+{
+    std::map<RString, size_t> known;
+    std::vector<RString> names;
+public:
+    size_t intern(XString name_)
+    {
+        // TODO just look up the XString, the memory should not move by now
+        RString name = name_;
+        // hm, I could change this to do aliases
+        auto pair = known.insert({name, known.size()});
+        if (pair.second)
+            names.push_back(name);
+        assert (known.size() == names.size());
+        return pair.first->second;
+    }
+
+    ZString outtern(size_t sz) const
+    {
+        return names[sz];
+    }
+
+    size_t size() const
+    {
+        return known.size();
+    }
+};
+
+#endif // TMWA_GENERIC_INTERN_POOL_HPP
diff --git a/src/generic/intern-pool_test.cpp b/src/generic/intern-pool_test.cpp
new file mode 100644
index 0000000..bf17b67
--- /dev/null
+++ b/src/generic/intern-pool_test.cpp
@@ -0,0 +1,20 @@
+#include "intern-pool.hpp"
+
+#include <gtest/gtest.h>
+
+#include "../strings/base.hpp"
+
+TEST(InternPool, whydoesthisalwaysneedasecondname)
+{
+    InternPool p;
+    EXPECT_EQ(0, p.size());
+    EXPECT_EQ(0, p.intern("hello"));
+    EXPECT_EQ(0, p.intern("hello"));
+    EXPECT_EQ(1, p.size());
+    EXPECT_EQ(1, p.intern("world"));
+    EXPECT_EQ(0, p.intern("hello"));
+    EXPECT_EQ(1, p.intern("world"));
+    EXPECT_EQ(2, p.size());
+    EXPECT_EQ("hello", p.outtern(0));
+    EXPECT_EQ("world", p.outtern(1));
+}
diff --git a/src/generic/matrix.cpp b/src/generic/matrix.cpp
new file mode 100644
index 0000000..9ee049e
--- /dev/null
+++ b/src/generic/matrix.cpp
@@ -0,0 +1,3 @@
+#include "matrix.hpp"
+
+#include "../poison.hpp"
diff --git a/src/generic/matrix.hpp b/src/generic/matrix.hpp
new file mode 100644
index 0000000..3530ba7
--- /dev/null
+++ b/src/generic/matrix.hpp
@@ -0,0 +1,54 @@
+#ifndef TMWA_GENERIC_MATRIX_HPP
+#define TMWA_GENERIC_MATRIX_HPP
+
+# include "../sanity.hpp"
+
+# include "../compat/memory.hpp"
+
+template<class T>
+class Matrix
+{
+    std::unique_ptr<T[]> _data;
+    size_t _xs, _ys;
+public:
+    Matrix()
+    : _data()
+    , _xs()
+    , _ys()
+    {}
+    Matrix(size_t x, size_t y)
+    : _data(make_unique<T[]>(x * y))
+    , _xs(x)
+    , _ys(y)
+    {}
+    // no copy-ctor or copy-assign
+
+    void reset(size_t x, size_t y)
+    {
+        *this = Matrix(x, y);
+    }
+    void clear()
+    {
+        *this = Matrix();
+    }
+
+    T& ref(size_t x, size_t y)
+    {
+        return _data[x + y * _xs];
+    }
+    const T& ref(size_t x, size_t y) const
+    {
+        return _data[x + y * _xs];
+    }
+
+    size_t xs() const
+    {
+        return _xs;
+    }
+    size_t ys() const
+    {
+        return _ys;
+    }
+};
+
+#endif // TMWA_GENERIC_MATRIX_HPP
diff --git a/src/generic/md5.cpp b/src/generic/md5.cpp
new file mode 100644
index 0000000..a626dc5
--- /dev/null
+++ b/src/generic/md5.cpp
@@ -0,0 +1,234 @@
+#include "md5.hpp"
+
+#include <cstring>
+
+#include "../compat/rawmem.hpp"
+
+#include "../strings/xstring.hpp"
+#include "../strings/vstring.hpp"
+
+#include "random.hpp"
+
+#include "../poison.hpp"
+
+// auxilary data
+/*
+sin() constant table
+#Reformatted output of:
+echo 'scale=40; obase=16; for (i=1;i<=64;i++) print 2^32 * sin(i), "\n"' |
+bc | sed 's/^-//;s/^/0x/;s/\..*$/,/'
+*/
+static
+const uint32_t T[64] =
+{
+    // used by round 1
+    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0
+    0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4
+    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8
+    0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12
+    // used by round 2
+    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16
+    0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, //20
+    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24
+    0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28
+    // used by round 3
+    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32
+    0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36
+    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, //40
+    0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44
+    // used by round 4
+    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48
+    0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52
+    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56
+    0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, //60
+};
+
+// auxilary functions
+// note - the RFC defines these by non-CS conventions: or=v, and=(empty)
+static
+uint32_t rotate_left(uint32_t val, unsigned shift)
+{
+    return val << shift | val >> (32 - shift);
+}
+
+static
+uint32_t F(uint32_t X, uint32_t Y, uint32_t Z)
+{
+    return (X & Y) | (~X & Z);
+}
+static
+uint32_t G(uint32_t X, uint32_t Y, uint32_t Z)
+{
+    return (X & Z) | (Y & ~Z);
+}
+static
+uint32_t H(uint32_t X, uint32_t Y, uint32_t Z)
+{
+    return X ^ Y ^ Z;
+}
+static
+uint32_t I(uint32_t X, uint32_t Y, uint32_t Z)
+{
+    return Y ^ (X | ~Z);
+}
+
+static
+const struct
+{
+    uint8_t k : 4;
+    uint8_t : 0;
+    uint8_t s : 5;
+//    uint8_t i : 6; just increments constantly, from 1 .. 64 over all rounds
+}
+MD5_round1[16] =
+{
+    { 0,  7}, { 1, 12}, { 2, 17}, { 3, 22},
+    { 4,  7}, { 5, 12}, { 6, 17}, { 7, 22},
+    { 8,  7}, { 9, 12}, {10, 17}, {11, 22},
+    {12,  7}, {13, 12}, {14, 17}, {15, 22},
+},
+MD5_round2[16] =
+{
+    { 1,  5}, { 6,  9}, {11, 14}, { 0, 20},
+    { 5,  5}, {10,  9}, {15, 14}, { 4, 20},
+    { 9,  5}, {14,  9}, { 3, 14}, { 8, 20},
+    {13,  5}, { 2,  9}, { 7, 14}, {12, 20},
+},
+MD5_round3[16] =
+{
+    { 5,  4}, { 8, 11}, {11, 16}, {14, 23},
+    { 1,  4}, { 4, 11}, { 7, 16}, {10, 23},
+    {13,  4}, { 0, 11}, { 3, 16}, { 6, 23},
+    { 9,  4}, {12, 11}, {15, 16}, { 2, 23},
+},
+MD5_round4[16] =
+{
+    { 0,  6}, { 7, 10}, {14, 15}, { 5, 21},
+    {12,  6}, { 3, 10}, {10, 15}, { 1, 21},
+    { 8,  6}, {15, 10}, { 6, 15}, {13, 21},
+    { 4,  6}, {11, 10}, { 2, 15}, { 9, 21},
+};
+
+
+void MD5_init(MD5_state* state)
+{
+    // in the RFC, these are specified as bytes, interpreted as little-endian
+    state->val[0] = 0x67452301;
+    state->val[1] = 0xEFCDAB89;
+    state->val[2] = 0x98BADCFE;
+    state->val[3] = 0x10325476;
+}
+
+#define X block.data
+
+void MD5_do_block(MD5_state* state, MD5_block block)
+{
+#define a state->val[(16 - i) % 4]
+#define b state->val[(17 - i) % 4]
+#define c state->val[(18 - i) % 4]
+#define d state->val[(19 - i) % 4]
+    // save the values
+    const MD5_state saved = *state;
+    // round 1
+    for (int i = 0; i < 16; i++)
+    {
+#define k MD5_round1[i].k
+#define s MD5_round1[i].s
+        a = b + rotate_left(a + F(b, c, d) + X[k] + T[i + 0x0], s);
+#undef k
+#undef s
+    }
+    // round 2
+    for (int i = 0; i < 16; i++)
+    {
+#define k MD5_round2[i].k
+#define s MD5_round2[i].s
+        a = b + rotate_left(a + G(b, c, d) + X[k] + T[i + 0x10], s);
+#undef k
+#undef s
+    }
+    // round 3
+    for (int i = 0; i < 16; i++)
+    {
+#define k MD5_round3[i].k
+#define s MD5_round3[i].s
+        a = b + rotate_left(a + H(b, c, d) + X[k] + T[i + 0x20], s);
+#undef k
+#undef s
+    }
+    // round 4
+    for (int i = 0; i < 16; i++)
+    {
+#define k MD5_round4[i].k
+#define s MD5_round4[i].s
+        a = b + rotate_left(a + I(b, c, d) + X[k] + T[i + 0x30], s);
+#undef k
+#undef s
+    }
+    // adjust state based on original
+    state->val[0] += saved.val[0];
+    state->val[1] += saved.val[1];
+    state->val[2] += saved.val[2];
+    state->val[3] += saved.val[3];
+#undef a
+#undef b
+#undef c
+#undef d
+}
+
+void MD5_to_bin(MD5_state state, md5_binary& out)
+{
+    for (int i = 0; i < 0x10; i++)
+        out[i] = state.val[i / 4] >> 8 * (i % 4);
+}
+
+static
+const char hex[] = "0123456789abcdef";
+
+void MD5_to_str(MD5_state state, md5_string& out_)
+{
+    md5_binary bin;
+    MD5_to_bin(state, bin);
+    char out[0x20];
+    for (int i = 0; i < 0x10; i++)
+        out[2 * i] = hex[bin[i] >> 4],
+        out[2 * i + 1] = hex[bin[i] & 0xf];
+    out_ = stringish<md5_string>(XString(out, out + 0x20, nullptr));
+}
+
+MD5_state MD5_from_string(XString msg)
+{
+    MD5_state state;
+    MD5_init(&state);
+    MD5_block block;
+    const uint64_t msg_full_len = msg.size();
+    while (msg.size() >= 64)
+    {
+        for (int i = 0; i < 0x10; i++)
+            X[i] = msg[4 * i + 0] | msg[4 * i + 1] << 8 | msg[4 * i + 2] << 16 | msg[4 * i + 3] << 24;
+        MD5_do_block(&state, block);
+        msg = msg.xslice_t(64);
+    }
+    // now pad 1-512 bits + the 64-bit length - may be two blocks
+    uint8_t buf[0x40] = {};
+    really_memcpy(buf, reinterpret_cast<const uint8_t *>(msg.data()), msg.size());
+    buf[msg.size()] = 0x80; // a single one bit
+    if (64 - msg.size() > 8)
+    {
+        for (int i = 0; i < 8; i++)
+            buf[0x38 + i] = (msg_full_len * 8) >> (i * 8);
+    }
+    for (int i = 0; i < 0x10; i++)
+        X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
+    MD5_do_block(&state, block);
+    if (64 - msg.size() <= 8)
+    {
+        really_memset0(buf, 0x38);
+        for (int i = 0; i < 8; i++)
+            buf[0x38 + i] = (msg_full_len * 8) >> (i * 8);
+        for (int i = 0; i < 0x10; i++)
+            X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
+        MD5_do_block(&state, block);
+    }
+    return state;
+}
diff --git a/src/generic/md5.hpp b/src/generic/md5.hpp
new file mode 100644
index 0000000..ba1212a
--- /dev/null
+++ b/src/generic/md5.hpp
@@ -0,0 +1,44 @@
+#ifndef TMWA_GENERIC_MD5CALC_HPP
+#define TMWA_GENERIC_MD5CALC_HPP
+
+# include "../sanity.hpp"
+
+# include <netinet/in.h>
+
+# include <cstdint>
+# include <cstddef>
+# include <cstdio>
+
+# include <array>
+
+# include "../strings/fwd.hpp"
+# include "../strings/vstring.hpp"
+
+/// The digest state - becomes the output
+struct MD5_state
+{
+    // classically named {A,B,C,D}
+    // but use an array so we can index
+    uint32_t val[4];
+};
+struct MD5_block
+{
+    uint32_t data[16];
+};
+
+struct md5_binary : std::array<uint8_t, 0x10> {};
+struct md5_string : VString<0x20> {};
+struct SaltString : VString<5> {};
+
+// Implementation
+void MD5_init(MD5_state *state);
+void MD5_do_block(MD5_state *state, MD5_block block);
+
+// Output formatting
+void MD5_to_bin(MD5_state state, md5_binary& out);
+void MD5_to_str(MD5_state state, md5_string& out);
+
+// Convenience
+MD5_state MD5_from_string(XString msg);
+
+#endif // TMWA_GENERIC_MD5CALC_HPP
diff --git a/src/generic/md5_test.cpp b/src/generic/md5_test.cpp
new file mode 100644
index 0000000..7086c8c
--- /dev/null
+++ b/src/generic/md5_test.cpp
@@ -0,0 +1,28 @@
+#include "md5.hpp"
+
+#include <gtest/gtest.h>
+
+#include "../strings/xstring.hpp"
+#include "../strings/vstring.hpp"
+
+// This should be made part of the main API,
+// but is not yet to keep the diff small.
+// Edit: hack to fix the new strict comparison.
+static
+VString<32> MD5(XString in)
+{
+    md5_string out;
+    MD5_to_str(MD5_from_string(in), out);
+    return out;
+}
+
+TEST(md5calc, rfc1321)
+{
+    EXPECT_EQ("d41d8cd98f00b204e9800998ecf8427e", MD5(""));
+    EXPECT_EQ("0cc175b9c0f1b6a831c399e269772661", MD5("a"));
+    EXPECT_EQ("900150983cd24fb0d6963f7d28e17f72", MD5("abc"));
+    EXPECT_EQ("f96b697d7cb7938d525a2f31aaf161d0", MD5("message digest"));
+    EXPECT_EQ("c3fcd3d76192e4007dfb496cca67e13b", MD5("abcdefghijklmnopqrstuvwxyz"));
+    EXPECT_EQ("d174ab98d277d9f5a5611c2c9f419d9f", MD5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"));
+    EXPECT_EQ("57edf4a22be3c955ac49da2e2107b67a", MD5("12345678901234567890123456789012345678901234567890123456789012345678901234567890"));
+}
diff --git a/src/generic/operators.cpp b/src/generic/operators.cpp
new file mode 100644
index 0000000..877aec6
--- /dev/null
+++ b/src/generic/operators.cpp
@@ -0,0 +1,3 @@
+#include "operators.hpp"
+
+#include "../poison.hpp"
diff --git a/src/generic/operators.hpp b/src/generic/operators.hpp
new file mode 100644
index 0000000..3d3dccd
--- /dev/null
+++ b/src/generic/operators.hpp
@@ -0,0 +1,47 @@
+#ifndef TMWA_GENERIC_OPERATORS_HPP
+#define TMWA_GENERIC_OPERATORS_HPP
+
+namespace _operators
+{
+    class Comparable {};
+
+    template<class T>
+    bool operator == (T l, T r)
+    {
+        return l.value == r.value;
+    }
+
+    template<class T>
+    bool operator != (T l, T r)
+    {
+        return l.value != r.value;
+    }
+
+    template<class T>
+    bool operator < (T l, T r)
+    {
+        return l.value < r.value;
+    }
+
+    template<class T>
+    bool operator <= (T l, T r)
+    {
+        return l.value <= r.value;
+    }
+
+    template<class T>
+    bool operator > (T l, T r)
+    {
+        return l.value > r.value;
+    }
+
+    template<class T>
+    bool operator >= (T l, T r)
+    {
+        return l.value >= r.value;
+    }
+}
+
+using _operators::Comparable;
+
+#endif // TMWA_GENERIC_OPERATORS_HPP
diff --git a/src/generic/random.cpp b/src/generic/random.cpp
new file mode 100644
index 0000000..273dcec
--- /dev/null
+++ b/src/generic/random.cpp
@@ -0,0 +1,8 @@
+#include "random2.hpp"
+
+#include "../poison.hpp"
+
+namespace random_
+{
+    std::mt19937 generate{std::random_device()()};
+} // namespace random_
diff --git a/src/generic/random.hpp b/src/generic/random.hpp
new file mode 100644
index 0000000..5d7a7af
--- /dev/null
+++ b/src/generic/random.hpp
@@ -0,0 +1,69 @@
+#ifndef TMWA_GENERIC_RANDOM_HPP
+#define TMWA_GENERIC_RANDOM_HPP
+
+# include "random.t.hpp"
+
+# include "../sanity.hpp"
+
+# include <random>
+
+// This is not namespace random since that collides with a C function,
+// but this can be revisited when everything goes into namespace tmwa.
+namespace random_
+{
+    /// Get a random number from 0 .. 2**32 - 1
+    extern std::mt19937 generate;
+
+    /// Get a random number from 0 .. bound - 1
+    inline
+    int to(int bound)
+    {
+        std::uniform_int_distribution<int> dist(0, bound - 1);
+        return dist(generate);
+    }
+
+    /// Get a random number from low .. high (inclusive!)
+    inline
+    int in(int low, int high)
+    {
+        std::uniform_int_distribution<int> dist(low, high);
+        return dist(generate);
+    }
+
+    inline
+    bool coin()
+    {
+        // sigh, can't specify <bool> directly ...
+        std::uniform_int_distribution<int> dist(false, true);
+        return dist(generate);
+    }
+
+    inline
+    bool chance(Fraction f)
+    {
+        if (f.num <= 0)
+            return false;
+        if (f.num >= f.den)
+            return true;
+        return random_::to(f.den) < f.num;
+    }
+
+    // C is usually one of:
+    //  std::vector<T>
+    //  std::initializer_list<T>
+    //  std::array<T, n>
+    template<class C>
+    auto choice(C&& c) -> decltype(*c.begin())
+    {
+        return *(c.begin() + random_::to(c.size()));
+    }
+
+    // allow bare braces
+    template<class T>
+    T choice(std::initializer_list<T>&& il)
+    {
+        return random_::choice(il);
+    }
+} // namespace random_
+
+#endif // TMWA_GENERIC_RANDOM_HPP
diff --git a/src/generic/random.t.hpp b/src/generic/random.t.hpp
new file mode 100644
index 0000000..feea2b0
--- /dev/null
+++ b/src/generic/random.t.hpp
@@ -0,0 +1,23 @@
+#ifndef TMWA_GENERIC_RANDOM_T_HPP
+#define TMWA_GENERIC_RANDOM_T_HPP
+
+namespace random_
+{
+    struct Fraction
+    {
+        int num, den;
+    };
+
+    template<class T, T den>
+    struct Fixed
+    {
+        T num;
+
+        operator Fraction()
+        {
+            return {num, den};
+        }
+    };
+} // namespace random_
+
+#endif // TMWA_GENERIC_RANDOM_T_HPP
diff --git a/src/generic/random2.hpp b/src/generic/random2.hpp
new file mode 100644
index 0000000..4bdc72e
--- /dev/null
+++ b/src/generic/random2.hpp
@@ -0,0 +1,75 @@
+#ifndef TMWA_GENERIC_RANDOM2_HPP
+#define TMWA_GENERIC_RANDOM2_HPP
+
+# include "random.hpp"
+
+# include <algorithm>
+
+# include "../compat/iter.hpp"
+
+namespace random_
+{
+    namespace detail
+    {
+        struct RandomIterator
+        {
+            int bound;
+            int current;
+            bool frist;
+
+            void operator ++()
+            {
+                frist = false;
+                // TODO: reimplement in terms of LFSRs, so that certain
+                // blockage patterns don't favor adjacent cells.
+                current = current + 1;
+                if (current == bound)
+                    current = 0;
+            }
+            int operator *()
+            {
+                return current;
+            }
+        };
+
+        inline
+        bool operator == (RandomIterator l, RandomIterator r)
+        {
+            return l.current == r.current && l.frist == r.frist;
+        }
+
+        inline
+        bool operator != (RandomIterator l, RandomIterator r)
+        {
+            return !(l == r);
+        }
+    }
+
+    /// Yield every cell from 0 .. bound - 1 in some order.
+    /// The starting position is random, but not the order itself.
+    ///
+    /// Expected usage:
+    ///     for (int i : random_::iterator(vec.size()))
+    ///         if (vec[i].okay())
+    ///             return frob(vec[i]);
+    inline
+    IteratorPair<detail::RandomIterator> iterator(int bound)
+    {
+        int current = random_::to(bound);
+        return
+        {
+            detail::RandomIterator{bound, current, true},
+            detail::RandomIterator{bound, current, false}
+        };
+    }
+
+    /// similar to std::random_shuffle(c.begin(), c.end()), but guaranteed
+    /// to use a good source of randomness.
+    template<class C>
+    void shuffle(C&& c)
+    {
+        std::random_shuffle(c.begin(), c.end(), random_::to);
+    }
+} // namespace random_
+
+#endif // TMWA_GENERIC_RANDOM2_HPP
diff --git a/src/io/cxxstdio.cpp b/src/io/cxxstdio.cpp
index 47617cb..aa603e4 100644
--- a/src/io/cxxstdio.cpp
+++ b/src/io/cxxstdio.cpp
@@ -1 +1,3 @@
 #include "cxxstdio.hpp"
+
+#include "../poison.hpp"
diff --git a/src/io/cxxstdio.hpp b/src/io/cxxstdio.hpp
index 753a1fc..0324ebc 100644
--- a/src/io/cxxstdio.hpp
+++ b/src/io/cxxstdio.hpp
@@ -24,9 +24,10 @@
 # include <cstdarg>
 # include <cstdio>
 
-// TODO get rid of these header order violations
-# include "../common/const_array.hpp"
-# include "../common/utils2.hpp"
+# include "../compat/cast.hpp"
+
+# include "../generic/const_array.hpp"
+# include "../generic/enum.hpp"
 
 # include "fwd.hpp"
 
@@ -213,27 +214,25 @@ namespace cxxstdio
         }
     };
 
-# define XPRINTF(out, fmt, ...)                                                         \
-    (/*[&]() -> int*/                                                                   \
-    {                                                                                   \
-        struct format_impl                                                              \
-        {                                                                               \
-            constexpr static                                                            \
-            const char *print_format() { return fmt; }                                  \
-        };                                                                              \
-        /*return*/ cxxstdio::PrintFormatter<format_impl>::print(out, ## __VA_ARGS__);   \
-    }/*()*/)
+# define XPRINTF(out, fmt, ...)                                             \
+    ({                                                                      \
+        struct format_impl                                                  \
+        {                                                                   \
+            constexpr static                                                \
+            const char *print_format() { return fmt; }                      \
+        };                                                                  \
+        cxxstdio::PrintFormatter<format_impl>::print(out, ## __VA_ARGS__);  \
+    })
 
-# define XSCANF(out, fmt, ...)                                                      \
-    (/*[&]() -> int*/                                                               \
-    {                                                                               \
-        struct format_impl                                                          \
-        {                                                                           \
-            constexpr static                                                        \
-            const char *scan_format() { return fmt; }                               \
-        };                                                                          \
-        /*return*/ cxxstdio::ScanFormatter<format_impl>::scan(out, ## __VA_ARGS__); \
-    }/*()*/)
+# define XSCANF(out, fmt, ...)                                              \
+    ({                                                                      \
+        struct format_impl                                                  \
+        {                                                                   \
+            constexpr static                                                \
+            const char *scan_format() { return fmt; }                       \
+        };                                                                  \
+        cxxstdio::ScanFormatter<format_impl>::scan(out, ## __VA_ARGS__);    \
+    })
 
 # define FPRINTF(file, fmt, ...)     XPRINTF(/*no_cast<FILE *>*/(file), fmt, ## __VA_ARGS__)
 # define FSCANF(file, fmt, ...)      XSCANF(no_cast<FILE *>(file), fmt, ## __VA_ARGS__)
@@ -241,23 +240,21 @@ namespace cxxstdio
 # define SPRINTF(str, fmt, ...)      XPRINTF(base_cast<AString&>(str), fmt, ## __VA_ARGS__)
 # define SNPRINTF(str, n, fmt, ...)  XPRINTF(base_cast<VString<n-1>&>(str), fmt, ## __VA_ARGS__)
 # define SCANF(fmt, ...)             FSCANF(stdin, fmt, ## __VA_ARGS__)
-# define SSCANF(str, fmt, ...)       XSCANF(/*ZString or compatible*/str, fmt, ## __VA_ARGS__)
+# define SSCANF(str, fmt, ...)       XSCANF(maybe_cast<ZString>(str), fmt, ## __VA_ARGS__)
 
 # define STRPRINTF(fmt, ...)                        \
-    (/*[&]() -> AString*/                           \
-    {                                               \
+    ({                                              \
         AString _out_impl;                          \
         SPRINTF(_out_impl, fmt, ## __VA_ARGS__);    \
-        /*return*/ _out_impl;                       \
-    }/*()*/)
+        _out_impl;                                  \
+    })
 
 # define STRNPRINTF(n, fmt, ...)                        \
-    (/*[&]() -> VString<n - 1>*/                        \
-    {                                                   \
+    ({                                                  \
         VString<n - 1> _out_impl;                       \
         SNPRINTF(_out_impl, n, fmt, ## __VA_ARGS__);    \
-        /*return*/ _out_impl;                           \
-    }/*()*/)
+        _out_impl;                                      \
+    })
 
 } // namespace cxxstdio
 
diff --git a/src/io/fd.cpp b/src/io/fd.cpp
index 87d3967..4fc33e9 100644
--- a/src/io/fd.cpp
+++ b/src/io/fd.cpp
@@ -23,6 +23,7 @@
 
 #include "../strings/zstring.hpp"
 
+#include "../poison.hpp"
 
 namespace io
 {
diff --git a/src/io/fd.hpp b/src/io/fd.hpp
index ca703d6..7afb40f 100644
--- a/src/io/fd.hpp
+++ b/src/io/fd.hpp
@@ -165,4 +165,4 @@ namespace io
     };
 } // namespace io
 
-#endif //TMWA_IO_FD_HPP
+#endif // TMWA_IO_FD_HPP
diff --git a/src/io/fwd.hpp b/src/io/fwd.hpp
index 52e6d6d..1e5fa82 100644
--- a/src/io/fwd.hpp
+++ b/src/io/fwd.hpp
@@ -29,4 +29,4 @@ namespace io
     class AppendFile;
 } // namespace io
 
-#endif //TMWA_IO_FWD_HPP
+#endif // TMWA_IO_FWD_HPP
diff --git a/src/io/line.hpp b/src/io/line.hpp
index bdff1bb..321cdf7 100644
--- a/src/io/line.hpp
+++ b/src/io/line.hpp
@@ -94,4 +94,4 @@ namespace io
     };
 } // namespace io
 
-#endif //TMWA_IO_LINE_HPP
+#endif // TMWA_IO_LINE_HPP
diff --git a/src/io/read.hpp b/src/io/read.hpp
index 2355e46..f99fb56 100644
--- a/src/io/read.hpp
+++ b/src/io/read.hpp
@@ -50,4 +50,4 @@ namespace io
     };
 } // namespace io
 
-#endif //TMWA_IO_READ_HPP
+#endif // TMWA_IO_READ_HPP
diff --git a/src/io/tty.cpp b/src/io/tty.cpp
index d2e084e..e71ee44 100644
--- a/src/io/tty.cpp
+++ b/src/io/tty.cpp
@@ -18,4 +18,6 @@
 //    You should have received a copy of the GNU General Public License
 //    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#include "../poison.hpp"
+
 /* Nothing to see here, move along */
diff --git a/src/io/tty.hpp b/src/io/tty.hpp
index 86616f1..97487b8 100644
--- a/src/io/tty.hpp
+++ b/src/io/tty.hpp
@@ -35,4 +35,4 @@
 
 # define SGR_RESET "\e[0m"
 
-#endif //TMWA_IO_TTY_HPP
+#endif // TMWA_IO_TTY_HPP
diff --git a/src/io/write.hpp b/src/io/write.hpp
index 0a238c0..870ebb5 100644
--- a/src/io/write.hpp
+++ b/src/io/write.hpp
@@ -66,4 +66,4 @@ namespace io
     int do_vprint(WriteFile& out, const char *fmt, va_list ap);
 } // namespace io
 
-#endif //TMWA_IO_WRITE_HPP
+#endif // TMWA_IO_WRITE_HPP
diff --git a/src/login/GNUmakefile b/src/login/GNUmakefile
deleted file mode 100644
index eb983e8..0000000
--- a/src/login/GNUmakefile
+++ /dev/null
@@ -1,7 +0,0 @@
-.SUFFIXES:
-bin/tmwa-login:
-	${MAKE} -C ../.. bin/tmwa-login
-clean:
-	rm -r ../../obj/login/
-%::
-	${MAKE} -C ../.. obj/login/$@
diff --git a/src/login/login.cpp b/src/login/login.cpp
index b21b028..c9b285f 100644
--- a/src/login/login.cpp
+++ b/src/login/login.cpp
@@ -22,23 +22,24 @@
 #include "../strings/xstring.hpp"
 #include "../strings/vstring.hpp"
 
+#include "../generic/db.hpp"
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/lock.hpp"
 #include "../io/read.hpp"
 #include "../io/tty.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/core.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/human_time_diff.hpp"
-#include "../common/md5calc.hpp"
-#include "../common/mmo.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/version.hpp"
-#include "../common/utils.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/core.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/human_time_diff.hpp"
+#include "../mmo/md5more.hpp"
+#include "../mmo/mmo.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/version.hpp"
+#include "../mmo/utils.hpp"
 
 #include "../poison.hpp"
 
diff --git a/src/login/main.cpp b/src/login/main.cpp
index 2ef17a0..470bcd1 100644
--- a/src/login/main.cpp
+++ b/src/login/main.cpp
@@ -1,2 +1,4 @@
 // dummy file to make Make dependencies work
 #include "login.hpp"
+
+#include "../poison.hpp"
diff --git a/src/map/GNUmakefile b/src/map/GNUmakefile
deleted file mode 100644
index 22f6daa..0000000
--- a/src/map/GNUmakefile
+++ /dev/null
@@ -1,7 +0,0 @@
-.SUFFIXES:
-bin/tmwa-map:
-	${MAKE} -C ../.. bin/tmwa-map
-clean:
-	rm -r ../../obj/map/
-%::
-	${MAKE} -C ../.. obj/map/$@
diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index 39e81a9..29bf0b1 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -4,27 +4,29 @@
 #include <cstring>
 #include <ctime>
 
+#include "../compat/nullpo.hpp"
+#include "../compat/fun.hpp"
+
 #include "../strings/mstring.hpp"
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 #include "../strings/vstring.hpp"
 
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 #include "../io/write.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/core.hpp"
-#include "../common/extract.hpp"
-#include "../common/human_time_diff.hpp"
-#include "../common/mmo.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/utils2.hpp"
-#include "../common/version.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/core.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/human_time_diff.hpp"
+#include "../mmo/mmo.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/version.hpp"
 
 #include "battle.hpp"
 #include "chrif.hpp"
diff --git a/src/map/atcommand.hpp b/src/map/atcommand.hpp
index c825976..16d1efc 100644
--- a/src/map/atcommand.hpp
+++ b/src/map/atcommand.hpp
@@ -1,9 +1,9 @@
-#ifndef ATCOMMAND_HPP
-#define ATCOMMAND_HPP
+#ifndef TMWA_MAP_ATCOMMAND_HPP
+#define TMWA_MAP_ATCOMMAND_HPP
 
 # include "../strings/fwd.hpp"
 
-# include "../common/const_array.hpp"
+# include "../generic/const_array.hpp"
 
 # include "map.hpp"
 
@@ -19,4 +19,4 @@ extern AString gm_log;
 
 void atcommand_config_write(ZString cfgName);
 
-#endif // ATCOMMAND_HPP
+#endif // TMWA_MAP_ATCOMMAND_HPP
diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index 59e798d..7e563b8 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -2,15 +2,18 @@
 
 #include <cstring>
 
+#include "../compat/alg.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/random.hpp"
+#include "../mmo/config_parse.hpp"
 
 #include "clif.hpp"
 #include "itemdb.hpp"
diff --git a/src/map/battle.hpp b/src/map/battle.hpp
index 243d7fb..fd452e8 100644
--- a/src/map/battle.hpp
+++ b/src/map/battle.hpp
@@ -1,11 +1,11 @@
-#ifndef BATTLE_HPP
-#define BATTLE_HPP
+#ifndef TMWA_MAP_BATTLE_HPP
+#define TMWA_MAP_BATTLE_HPP
 
 # include "battle.t.hpp"
 
 # include "../strings/fwd.hpp"
 
-# include "../common/timer.t.hpp"
+# include "../mmo/timer.t.hpp"
 
 # include "magic-interpreter.t.hpp"
 # include "map.t.hpp"
@@ -204,4 +204,4 @@ extern struct Battle_Config
 bool battle_config_read(ZString cfgName);
 void battle_config_check();
 
-#endif // BATTLE_HPP
+#endif // TMWA_MAP_BATTLE_HPP
diff --git a/src/map/battle.t.hpp b/src/map/battle.t.hpp
index 1242c85..76ff571 100644
--- a/src/map/battle.t.hpp
+++ b/src/map/battle.t.hpp
@@ -1,7 +1,7 @@
-#ifndef BATTLE_T_HPP
-#define BATTLE_T_HPP
+#ifndef TMWA_MAP_BATTLE_T_HPP
+#define TMWA_MAP_BATTLE_T_HPP
 
-# include "../common/utils2.hpp"
+# include "../generic/enum.hpp"
 
 namespace e
 {
@@ -226,4 +226,4 @@ enum class DamageType : uint8_t
     FLEE2       = 0x0b,
 };
 
-#endif // BATTLE_T_HPP
+#endif // TMWA_MAP_BATTLE_T_HPP
diff --git a/src/map/chrif.cpp b/src/map/chrif.cpp
index 48cff81..ea74ca4 100644
--- a/src/map/chrif.cpp
+++ b/src/map/chrif.cpp
@@ -4,15 +4,17 @@
 
 #include <cstring>
 
+#include "../compat/fun.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 
 #include "../io/cxxstdio.hpp"
 
-#include "../common/nullpo.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/utils.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/utils.hpp"
 
 #include "battle.hpp"
 #include "clif.hpp"
diff --git a/src/map/chrif.hpp b/src/map/chrif.hpp
index 734f1ac..2dfda43 100644
--- a/src/map/chrif.hpp
+++ b/src/map/chrif.hpp
@@ -1,11 +1,11 @@
-#ifndef CHRIF_HPP
-#define CHRIF_HPP
+#ifndef TMWA_MAP_CHRIF_HPP
+#define TMWA_MAP_CHRIF_HPP
 
 # include "../strings/fwd.hpp"
 
-# include "../common/dumb_ptr.hpp"
-# include "../common/human_time_diff.hpp"
-# include "../common/ip.hpp"
+# include "../mmo/dumb_ptr.hpp"
+# include "../mmo/human_time_diff.hpp"
+# include "../mmo/ip.hpp"
 
 # include "map.hpp"
 
@@ -39,4 +39,4 @@ void do_init_chrif(void);
 // only used by intif.cpp
 extern Session *char_session;
 
-#endif // CHRIF_HPP
+#endif // TMWA_MAP_CHRIF_HPP
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 37646bf..3ec41ae 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -6,19 +6,24 @@
 #include <cstring>
 #include <ctime>
 
+#include "../compat/alg.hpp"
+#include "../compat/attr.hpp"
+#include "../compat/fun.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/write.hpp"
 
-#include "../common/md5calc.hpp"
-#include "../common/random.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/version.hpp"
+#include "../mmo/md5more.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/version.hpp"
 
 #include "atcommand.hpp"
 #include "battle.hpp"
diff --git a/src/map/clif.hpp b/src/map/clif.hpp
index e84adf3..c346f7d 100644
--- a/src/map/clif.hpp
+++ b/src/map/clif.hpp
@@ -1,5 +1,5 @@
-#ifndef CLIF_HPP
-#define CLIF_HPP
+#ifndef TMWA_MAP_CLIF_HPP
+#define TMWA_MAP_CLIF_HPP
 
 # include "clif.t.hpp"
 
@@ -7,9 +7,10 @@
 
 # include "../strings/fwd.hpp"
 
-# include "../common/const_array.hpp"
-# include "../common/ip.hpp"
-# include "../common/timer.t.hpp"
+# include "../generic/const_array.hpp"
+
+# include "../mmo/ip.hpp"
+# include "../mmo/timer.t.hpp"
 
 # include "battle.t.hpp"
 # include "map.hpp"
@@ -163,4 +164,4 @@ int clif_foreachclient(std::function<void(dumb_ptr<map_session_data>)>);
 
 void do_init_clif(void);
 
-#endif // CLIF_HPP
+#endif // TMWA_MAP_CLIF_HPP
diff --git a/src/map/clif.t.hpp b/src/map/clif.t.hpp
index 96b8a9c..a52b33a 100644
--- a/src/map/clif.t.hpp
+++ b/src/map/clif.t.hpp
@@ -1,5 +1,5 @@
-#ifndef CLIF_T_HPP
-#define CLIF_T_HPP
+#ifndef TMWA_MAP_CLIF_T_HPP
+#define TMWA_MAP_CLIF_T_HPP
 
 # include <cstdint>
 
@@ -18,4 +18,4 @@ enum class BeingRemoveWhy : uint8_t
     NEGATIVE1 = 0xff,
 };
 
-#endif // CLIF_T_HPP
+#endif // TMWA_MAP_CLIF_T_HPP
diff --git a/src/map/grfio.cpp b/src/map/grfio.cpp
index d89fe21..bf55cf7 100644
--- a/src/map/grfio.cpp
+++ b/src/map/grfio.cpp
@@ -19,7 +19,7 @@
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/extract.hpp"
+#include "../mmo/extract.hpp"
 
 #include "../poison.hpp"
 
diff --git a/src/map/grfio.hpp b/src/map/grfio.hpp
index 3fde76a..e0758fe 100644
--- a/src/map/grfio.hpp
+++ b/src/map/grfio.hpp
@@ -1,11 +1,11 @@
-#ifndef GRFIO_HPP
-#define GRFIO_HPP
+#ifndef TMWA_MAP_GRFIO_HPP
+#define TMWA_MAP_GRFIO_HPP
 
 # include <cstdint>
 
 # include <vector>
 
-# include "../common/mmo.hpp"
+# include "../mmo/mmo.hpp"
 
 bool load_resnametable(ZString filename);
 
@@ -14,4 +14,4 @@ bool load_resnametable(ZString filename);
 /// Currently there is exactly one .wlk per .gat, but multiples are fine.
 std::vector<uint8_t> grfio_reads(MapName resourcename);
 
-#endif // GRFIO_HPP
+#endif // TMWA_MAP_GRFIO_HPP
diff --git a/src/map/intif.cpp b/src/map/intif.cpp
index 5c6c46a..7f7be73 100644
--- a/src/map/intif.cpp
+++ b/src/map/intif.cpp
@@ -3,14 +3,15 @@
 #include <cstdlib>
 #include <cstring>
 
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
 #include "../io/cxxstdio.hpp"
 
-#include "../common/nullpo.hpp"
-#include "../common/socket.hpp"
+#include "../mmo/socket.hpp"
 
 #include "battle.hpp"
 #include "chrif.hpp"
diff --git a/src/map/intif.hpp b/src/map/intif.hpp
index 0fe7182..ac2740c 100644
--- a/src/map/intif.hpp
+++ b/src/map/intif.hpp
@@ -1,9 +1,9 @@
-#ifndef INTIF_HPP
-#define INTIF_HPP
+#ifndef TMWA_MAP_INTIF_HPP
+#define TMWA_MAP_INTIF_HPP
 
 # include "../strings/fwd.hpp"
 
-# include "../common/const_array.hpp"
+# include "../generic/const_array.hpp"
 
 # include "map.hpp"
 
@@ -30,4 +30,4 @@ void intif_party_changemap(dumb_ptr<map_session_data> sd, int online);
 void intif_party_message(int party_id, int account_id, XString mes);
 void intif_party_checkconflict(int party_id, int account_id, CharName nick);
 
-#endif // INTIF_HPP
+#endif // TMWA_MAP_INTIF_HPP
diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp
index 330bd8b..528c81f 100644
--- a/src/map/itemdb.cpp
+++ b/src/map/itemdb.cpp
@@ -3,19 +3,21 @@
 #include <cstdlib>
 #include <cstring>
 
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
 
 #include "../poison.hpp"
 
diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp
index f026f95..4c07303 100644
--- a/src/map/itemdb.hpp
+++ b/src/map/itemdb.hpp
@@ -1,7 +1,7 @@
-#ifndef ITEMDB_HPP
-#define ITEMDB_HPP
+#ifndef TMWA_MAP_ITEMDB_HPP
+#define TMWA_MAP_ITEMDB_HPP
 
-# include "../common/mmo.hpp"
+# include "../mmo/mmo.hpp"
 
 # include "map.t.hpp"
 # include "script.hpp"
@@ -77,4 +77,4 @@ void itemdb_reload(void);
 void do_final_itemdb(void);
 bool itemdb_readdb(ZString filename);
 
-#endif // ITEMDB_HPP
+#endif // TMWA_MAP_ITEMDB_HPP
diff --git a/src/map/magic-expr-eval.hpp b/src/map/magic-expr-eval.hpp
index 1569be8..0783dc8 100644
--- a/src/map/magic-expr-eval.hpp
+++ b/src/map/magic-expr-eval.hpp
@@ -1,10 +1,8 @@
-#ifndef MAGIC_EXPR_EVAL_HPP
-#define MAGIC_EXPR_EVAL_HPP
+#ifndef TMWA_MAP_MAGIC_EXPR_EVAL_HPP
+#define TMWA_MAP_MAGIC_EXPR_EVAL_HPP
 
 # include "../strings/zstring.hpp"
 
-# include "../common/utils2.hpp"
-
 # include "magic-interpreter.hpp"
 
 /* Helper definitions for dealing with functions and operations */
@@ -42,4 +40,4 @@ void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height,
 
 # define ARG_MAY_BE_AREA(x) (ARG_TYPE(x) == TYPE::AREA || ARG_TYPE(x) == TYPE::LOCATION)
 
-#endif // MAGIC_EXPR_EVAL_HPP
+#endif // TMWA_MAP_MAGIC_EXPR_EVAL_HPP
diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp
index 1f9ef82..279ba56 100644
--- a/src/map/magic-expr.cpp
+++ b/src/map/magic-expr.cpp
@@ -5,14 +5,16 @@
 #include <cassert>
 #include <cmath>
 
+#include "../compat/alg.hpp"
+
 #include "../strings/mstring.hpp"
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/vstring.hpp"
 
-#include "../io/cxxstdio.hpp"
+#include "../generic/random.hpp"
 
-#include "../common/random.hpp"
+#include "../io/cxxstdio.hpp"
 
 #include "battle.hpp"
 #include "npc.hpp"
diff --git a/src/map/magic-expr.hpp b/src/map/magic-expr.hpp
index 0fdc435..79fdc3f 100644
--- a/src/map/magic-expr.hpp
+++ b/src/map/magic-expr.hpp
@@ -1,5 +1,5 @@
-#ifndef MAGIC_EXPR_HPP
-#define MAGIC_EXPR_HPP
+#ifndef TMWA_MAP_MAGIC_EXPR_HPP
+#define TMWA_MAP_MAGIC_EXPR_HPP
 
 # include "magic-interpreter.hpp"
 
@@ -84,4 +84,4 @@ int magic_find_item(const_array<val_t> args, int index, struct item *item, int *
 
 int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area);
 
-#endif // MAGIC_EXPR_HPP
+#endif // TMWA_MAP_MAGIC_EXPR_HPP
diff --git a/src/map/magic-interpreter-aux.hpp b/src/map/magic-interpreter-aux.hpp
index 070c771..6f5ad79 100644
--- a/src/map/magic-interpreter-aux.hpp
+++ b/src/map/magic-interpreter-aux.hpp
@@ -1,5 +1,5 @@
-#ifndef MAGIC_INTERPRETER_AUX_HPP
-#define MAGIC_INTERPRETER_AUX_HPP
+#ifndef TMWA_MAP_MAGIC_INTERPRETER_AUX_HPP
+#define TMWA_MAP_MAGIC_INTERPRETER_AUX_HPP
 
 # include "magic-interpreter.t.hpp"
 
@@ -9,4 +9,4 @@ bool CHECK_TYPE(T *v, TYPE t)
     return v->ty == t;
 }
 
-#endif // MAGIC_INTERPRETER_AUX_HPP
+#endif // TMWA_MAP_MAGIC_INTERPRETER_AUX_HPP
diff --git a/src/map/magic-interpreter-base.cpp b/src/map/magic-interpreter-base.cpp
index f1878a0..40f186b 100644
--- a/src/map/magic-interpreter-base.cpp
+++ b/src/map/magic-interpreter-base.cpp
@@ -8,7 +8,7 @@
 
 #include "../io/cxxstdio.hpp"
 
-#include "../common/timer.hpp"
+#include "../mmo/timer.hpp"
 
 #include "magic-expr.hpp"
 
diff --git a/src/map/magic-interpreter-parser.ypp b/src/map/magic-interpreter-parser.ypp
index 3ec1bdd..ef8b159 100644
--- a/src/map/magic-interpreter-parser.ypp
+++ b/src/map/magic-interpreter-parser.ypp
@@ -17,9 +17,9 @@ AString current_magic_filename;
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 
-#include "../io/cxxstdio.hpp"
+#include "../generic/const_array.hpp"
 
-#include "../common/const_array.hpp"
+#include "../io/cxxstdio.hpp"
 
 #include "itemdb.hpp"
 #include "magic-expr.hpp"
diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp
index 7e42499..6864844 100644
--- a/src/map/magic-interpreter.hpp
+++ b/src/map/magic-interpreter.hpp
@@ -1,5 +1,5 @@
-#ifndef MAGIC_INTERPRETER_HPP
-#define MAGIC_INTERPRETER_HPP
+#ifndef TMWA_MAP_MAGIC_INTERPRETER_HPP
+#define TMWA_MAP_MAGIC_INTERPRETER_HPP
 
 # include "magic-interpreter.t.hpp"
 
@@ -450,4 +450,4 @@ bool magic_init0();
 bool magic_init1(ZString filename);
 void spell_update_location(dumb_ptr<invocation> invocation);
 
-#endif // MAGIC_INTERPRETER_HPP
+#endif // TMWA_MAP_MAGIC_INTERPRETER_HPP
diff --git a/src/map/magic-interpreter.t.hpp b/src/map/magic-interpreter.t.hpp
index fb7bb6d..e8d7e06 100644
--- a/src/map/magic-interpreter.t.hpp
+++ b/src/map/magic-interpreter.t.hpp
@@ -1,7 +1,7 @@
-#ifndef MAGIC_INTERPRETER_T_HPP
-#define MAGIC_INTERPRETER_T_HPP
+#ifndef TMWA_MAP_MAGIC_INTERPRETER_T_HPP
+#define TMWA_MAP_MAGIC_INTERPRETER_T_HPP
 
-# include "../common/utils2.hpp"
+# include "../generic/enum.hpp"
 
 enum class SPELLARG : uint8_t
 {
@@ -156,4 +156,4 @@ ENUM_BITWISE_OPERATORS(INVOCATION_FLAG)
 }
 using e::INVOCATION_FLAG;
 
-#endif // MAGIC_INTERPRETER_T_HPP
+#endif // TMWA_MAP_MAGIC_INTERPRETER_T_HPP
diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp
index a71f43c..4063a68 100644
--- a/src/map/magic-stmt.cpp
+++ b/src/map/magic-stmt.cpp
@@ -2,12 +2,16 @@
 
 #include <cassert>
 
+#include "../compat/attr.hpp"
+#include "../compat/fun.hpp"
+
 #include "../strings/zstring.hpp"
 
+#include "../generic/random2.hpp"
+
 #include "../io/cxxstdio.hpp"
 
-#include "../common/random2.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/timer.hpp"
 
 #include "magic-expr.hpp"
 #include "magic-expr-eval.hpp"
diff --git a/src/map/magic.hpp b/src/map/magic.hpp
index 1fabd6f..1562469 100644
--- a/src/map/magic.hpp
+++ b/src/map/magic.hpp
@@ -1,9 +1,9 @@
-#ifndef MAGIC_HPP
-#define MAGIC_HPP
+#ifndef TMWA_MAP_MAGIC_HPP
+#define TMWA_MAP_MAGIC_HPP
 
 # include "../strings/fwd.hpp"
 
-# include "../common/dumb_ptr.hpp"
+# include "../mmo/dumb_ptr.hpp"
 
 # include "map.hpp"
 # include "skill.t.hpp"
@@ -80,4 +80,4 @@ int spell_attack(int caster, int target);
 
 void spell_free_invocation(dumb_ptr<invocation> invocation);
 
-#endif // MAGIC_HPP
+#endif // TMWA_MAP_MAGIC_HPP
diff --git a/src/map/main.cpp b/src/map/main.cpp
index c930630..f0e3517 100644
--- a/src/map/main.cpp
+++ b/src/map/main.cpp
@@ -1,2 +1,4 @@
 // dummy file to make Make dependencies work
 #include "map.hpp"
+
+#include "../poison.hpp"
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 132afac..d9dd9cc 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -10,25 +10,28 @@
 #include <cstdlib>
 #include <cstring>
 
+#include "../compat/nullpo.hpp"
+#include "../compat/fun.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 #include "../strings/vstring.hpp"
 
+#include "../generic/db.hpp"
+#include "../generic/random2.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 #include "../io/tty.hpp"
 #include "../io/write.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/core.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/random2.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
-#include "../common/version.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/core.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
+#include "../mmo/version.hpp"
 
 #include "atcommand.hpp"
 #include "battle.hpp"
diff --git a/src/map/map.hpp b/src/map/map.hpp
index 1964a3c..d476dbc 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -1,5 +1,5 @@
-#ifndef MAP_HPP
-#define MAP_HPP
+#ifndef TMWA_MAP_MAP_HPP
+#define TMWA_MAP_MAP_HPP
 
 # include "map.t.hpp"
 
@@ -13,12 +13,13 @@
 # include "../strings/astring.hpp"
 # include "../strings/vstring.hpp"
 
+# include "../generic/db.hpp"
+# include "../generic/matrix.hpp"
+
 # include "../io/cxxstdio.hpp"
 
-# include "../common/db.hpp"
-# include "../common/matrix.hpp"
-# include "../common/socket.hpp"
-# include "../common/timer.t.hpp"
+# include "../mmo/socket.hpp"
+# include "../mmo/timer.t.hpp"
 
 # include "battle.t.hpp"
 # include "magic-interpreter.t.hpp"
@@ -714,4 +715,4 @@ inline dumb_ptr<npc_data_shop> npc_data::is_shop() { return npc_subtype == NpcSu
 inline dumb_ptr<npc_data_warp> npc_data::is_warp() { return npc_subtype == NpcSubtype::WARP ? as_warp() : nullptr ; }
 inline dumb_ptr<npc_data_message> npc_data::is_message() { return npc_subtype == NpcSubtype::MESSAGE ? as_message() : nullptr ; }
 
-#endif // MAP_HPP
+#endif // TMWA_MAP_MAP_HPP
diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp
index f4d916a..4a41ea8 100644
--- a/src/map/map.t.hpp
+++ b/src/map/map.t.hpp
@@ -1,10 +1,9 @@
-#ifndef MAP_T_HPP
-#define MAP_T_HPP
+#ifndef TMWA_MAP_MAP_T_HPP
+#define TMWA_MAP_MAP_T_HPP
 
 # include "../strings/vstring.hpp"
 
-# include "../common/mmo.hpp"
-# include "../common/utils2.hpp"
+# include "../mmo/mmo.hpp"
 
 namespace e
 {
@@ -562,4 +561,4 @@ struct NpcName : VString<23> {};
 struct ScriptLabel : VString<23> {};
 struct ItemName : VString<23> {};
 
-#endif // MAP_T_HPP
+#endif // TMWA_MAP_MAP_T_HPP
diff --git a/src/map/mapflag.hpp b/src/map/mapflag.hpp
index bf8229f..f3819ff 100644
--- a/src/map/mapflag.hpp
+++ b/src/map/mapflag.hpp
@@ -21,7 +21,7 @@
 
 # include "../sanity.hpp"
 
-# include "../common/extract.hpp" // TODO remove this (requires specializing the *other* half)
+# include "../mmo/extract.hpp" // TODO remove this (requires specializing the *other* half)
 
 # include "../strings/xstring.hpp"
 
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index 5a414be..61dcfb6 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -8,18 +8,21 @@
 
 #include <algorithm>
 
+#include "../compat/fun.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/extract.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
 
 #include "battle.hpp"
 #include "clif.hpp"
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index bc8350a..305cd16 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -1,11 +1,12 @@
-#ifndef MOB_HPP
-#define MOB_HPP
+#ifndef TMWA_MAP_MOB_HPP
+#define TMWA_MAP_MOB_HPP
 
 # include "mob.t.hpp"
 
-# include "../common/mmo.hpp"
-# include "../common/timer.t.hpp"
-# include "../common/random.t.hpp"
+# include "../generic/random.t.hpp"
+
+# include "../mmo/mmo.hpp"
+# include "../mmo/timer.t.hpp"
 
 # include "clif.t.hpp"
 # include "map.hpp"
@@ -108,4 +109,4 @@ int mob_summonslave(dumb_ptr<mob_data> md2, int *value, int amount, int flag);
 
 void mob_reload(void);
 
-#endif // MOB_HPP
+#endif // TMWA_MAP_MOB_HPP
diff --git a/src/map/mob.t.hpp b/src/map/mob.t.hpp
index 54c326d..c8b1e7a 100644
--- a/src/map/mob.t.hpp
+++ b/src/map/mob.t.hpp
@@ -1,5 +1,5 @@
-#ifndef MOB_T_HPP
-#define MOB_T_HPP
+#ifndef TMWA_MAP_MOB_T_HPP
+#define TMWA_MAP_MOB_T_HPP
 
 # include <cstdint>
 
@@ -38,4 +38,4 @@ enum class MobSkillState : uint8_t
     MSS_CHASE,
 };
 
-#endif // MOB_T_HPP
+#endif // TMWA_MAP_MOB_T_HPP
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 8ecd7a3..1e164a5 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -7,20 +7,23 @@
 
 #include <list>
 
+#include "../compat/fun.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/mstring.hpp"
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
 
 #include "battle.hpp"
 #include "clif.hpp"
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index 4d4cef1..93f2030 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -1,12 +1,12 @@
-#ifndef NPC_HPP
-#define NPC_HPP
+#ifndef TMWA_MAP_NPC_HPP
+#define TMWA_MAP_NPC_HPP
 
 # include <cstddef>
 # include <cstdint>
 
 # include "../strings/fwd.hpp"
 
-# include "../common/timer.t.hpp"
+# include "../mmo/timer.t.hpp"
 
 # include "map.hpp"
 
@@ -70,4 +70,4 @@ interval_t npc_gettimerevent_tick(dumb_ptr<npc_data_script> nd);
 void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer);
 int npc_delete(dumb_ptr<npc_data> nd);
 
-#endif // NPC_HPP
+#endif // TMWA_MAP_NPC_HPP
diff --git a/src/map/party.cpp b/src/map/party.cpp
index f7b8396..eadf392 100644
--- a/src/map/party.cpp
+++ b/src/map/party.cpp
@@ -2,14 +2,16 @@
 
 #include <cstring>
 
+#include "../compat/nullpo.hpp"
+
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+
 #include "../io/cxxstdio.hpp"
 
-#include "../common/db.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
 
 #include "battle.hpp"
 #include "clif.hpp"
diff --git a/src/map/party.hpp b/src/map/party.hpp
index 91dab1b..c2d4e9d 100644
--- a/src/map/party.hpp
+++ b/src/map/party.hpp
@@ -1,5 +1,5 @@
-#ifndef PARTY_HPP
-#define PARTY_HPP
+#ifndef TMWA_MAP_PARTY_HPP
+#define TMWA_MAP_PARTY_HPP
 
 # include <functional>
 
@@ -48,4 +48,4 @@ int party_exp_share(struct party *p, map_local *map, int base_exp, int job_exp);
 void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func,
         dumb_ptr<map_session_data> sd, int type);
 
-#endif // PARTY_HPP
+#endif // TMWA_MAP_PARTY_HPP
diff --git a/src/map/path.cpp b/src/map/path.cpp
index d66640e..a237fa8 100644
--- a/src/map/path.cpp
+++ b/src/map/path.cpp
@@ -2,10 +2,11 @@
 
 #include <cassert>
 
-#include "../io/cxxstdio.hpp"
+#include "../compat/nullpo.hpp"
+
+#include "../generic/random.hpp"
 
-#include "../common/random.hpp"
-#include "../common/nullpo.hpp"
+#include "../io/cxxstdio.hpp"
 
 #include "battle.hpp"
 
diff --git a/src/map/path.hpp b/src/map/path.hpp
index 4b5b3d8..69316e9 100644
--- a/src/map/path.hpp
+++ b/src/map/path.hpp
@@ -1,8 +1,8 @@
-#ifndef PATH_HPP
-#define PATH_HPP
+#ifndef TMWA_MAP_PATH_HPP
+#define TMWA_MAP_PATH_HPP
 
 # include "map.hpp"
 
 int path_search(struct walkpath_data *, map_local *, int, int, int, int, int);
 
-#endif // PATH_HPP
+#endif // TMWA_MAP_PATH_HPP
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 86280ed..e3dd67a 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -4,17 +4,21 @@
 #include <cstdlib>
 #include <cstring>
 
+#include "../compat/alg.hpp"
+#include "../compat/fun.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/rstring.hpp"
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/nullpo.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
 
 #include "atcommand.hpp"
 #include "battle.hpp"
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index df612d8..0226019 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -1,5 +1,5 @@
-#ifndef PC_HPP
-#define PC_HPP
+#ifndef TMWA_MAP_PC_HPP
+#define TMWA_MAP_PC_HPP
 
 # include "pc.t.hpp"
 
@@ -155,4 +155,4 @@ void pc_show_motd(dumb_ptr<map_session_data> sd);
 
 void do_init_pc(void);
 
-#endif // PC_HPP
+#endif // TMWA_MAP_PC_HPP
diff --git a/src/map/pc.t.hpp b/src/map/pc.t.hpp
index 95a76a7..29c3515 100644
--- a/src/map/pc.t.hpp
+++ b/src/map/pc.t.hpp
@@ -1,5 +1,5 @@
-#ifndef PC_T_HPP
-#define PC_T_HPP
+#ifndef TMWA_MAP_PC_T_HPP
+#define TMWA_MAP_PC_T_HPP
 
 # include <cstdint>
 
@@ -41,4 +41,4 @@ enum class PickupFail : uint8_t
     DROP_STEAL  = 6,
 };
 
-#endif // PC_T_HPP
+#endif // TMWA_MAP_PC_T_HPP
diff --git a/src/map/script.cpp b/src/map/script.cpp
index e3db7c7..fe42e44 100644
--- a/src/map/script.cpp
+++ b/src/map/script.cpp
@@ -7,25 +7,28 @@
 #include <cstring>
 #include <ctime>
 
+#include "../compat/fun.hpp"
+
 #include "../strings/mstring.hpp"
 #include "../strings/rstring.hpp"
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/db.hpp"
+#include "../generic/intern-pool.hpp"
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/lock.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/core.hpp"
-#include "../common/db.hpp"
-#include "../common/extract.hpp"
-#include "../common/intern-pool.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
-#include "../common/utils.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/core.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/utils.hpp"
+#include "../mmo/timer.hpp"
 
 #include "atcommand.hpp"
 #include "battle.hpp"
diff --git a/src/map/script.hpp b/src/map/script.hpp
index 91ee6a5..6cf40b0 100644
--- a/src/map/script.hpp
+++ b/src/map/script.hpp
@@ -1,5 +1,5 @@
-#ifndef SCRIPT_HPP
-#define SCRIPT_HPP
+#ifndef TMWA_MAP_SCRIPT_HPP
+#define TMWA_MAP_SCRIPT_HPP
 
 # include <cstdint>
 # include <cstring> // for inlined get_str - TODO remove
@@ -10,9 +10,10 @@
 # include "../strings/astring.hpp"
 # include "../strings/zstring.hpp"
 
-# include "../common/db.hpp"
-# include "../common/dumb_ptr.hpp"
-# include "../common/utils.hpp"
+# include "../generic/db.hpp"
+
+# include "../mmo/dumb_ptr.hpp"
+# include "../mmo/utils.hpp"
 
 # include "map.t.hpp"
 
@@ -175,4 +176,4 @@ void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString
 int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e);
 ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e);
 
-#endif // SCRIPT_HPP
+#endif // TMWA_MAP_SCRIPT_HPP
diff --git a/src/map/script.py b/src/map/script.py
index ba0198c..068307a 100644
--- a/src/map/script.py
+++ b/src/map/script.py
@@ -54,3 +54,6 @@ class script_data(object):
             yield 'str', v['str']
         else:
             yield 'numi', v['numi']
+
+    def to_string(self):
+        return None
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index 077bf66..72d7f7a 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -5,18 +5,22 @@
 #include <cstring>
 #include <ctime>
 
+#include "../compat/attr.hpp"
+#include "../compat/fun.hpp"
+#include "../compat/nullpo.hpp"
+
 #include "../strings/mstring.hpp"
 #include "../strings/rstring.hpp"
 #include "../strings/xstring.hpp"
 
+#include "../generic/random.hpp"
+
 #include "../io/cxxstdio.hpp"
 #include "../io/read.hpp"
 
-#include "../common/extract.hpp"
-#include "../common/nullpo.hpp"
-#include "../common/random.hpp"
-#include "../common/socket.hpp"
-#include "../common/timer.hpp"
+#include "../mmo/extract.hpp"
+#include "../mmo/socket.hpp"
+#include "../mmo/timer.hpp"
 
 #include "battle.hpp"
 #include "clif.hpp"
diff --git a/src/map/skill.hpp b/src/map/skill.hpp
index e8f36ab..b612268 100644
--- a/src/map/skill.hpp
+++ b/src/map/skill.hpp
@@ -1,5 +1,5 @@
-#ifndef SKILL_HPP
-#define SKILL_HPP
+#ifndef TMWA_MAP_SKILL_HPP
+#define TMWA_MAP_SKILL_HPP
 
 # include "skill.t.hpp"
 # include "skill-pools.hpp"
@@ -143,4 +143,4 @@ int skill_power_bl(dumb_ptr<block_list> bl, SkillID skill);
 
 // [Fate] Remember that a certain skill ID belongs to a pool skill
 void skill_pool_register(SkillID id);
-#endif // SKILL_HPP
+#endif // TMWA_MAP_SKILL_HPP
diff --git a/src/map/skill.t.hpp b/src/map/skill.t.hpp
index 1470d61..aedbb11 100644
--- a/src/map/skill.t.hpp
+++ b/src/map/skill.t.hpp
@@ -1,9 +1,9 @@
-#ifndef SKILL_T_HPP
-#define SKILL_T_HPP
+#ifndef TMWA_MAP_SKILL_T_HPP
+#define TMWA_MAP_SKILL_T_HPP
 
 # include <cstdint>
 
-# include "../common/utils2.hpp"
+# include "../generic/enum.hpp"
 
 // TODO remove most of these as their corresponding SkillIDs get deleted.
 enum class StatusChange : uint16_t
@@ -110,4 +110,4 @@ ENUM_BITWISE_OPERATORS(SkillFlags)
 }
 using e::SkillFlags;
 
-#endif // SKILL_T_HPP
+#endif // TMWA_MAP_SKILL_T_HPP
diff --git a/src/map/storage.cpp b/src/map/storage.cpp
index b753c0d..89357b9 100644
--- a/src/map/storage.cpp
+++ b/src/map/storage.cpp
@@ -1,13 +1,13 @@
+#include "storage.hpp"
 // Copyright (c) Athena Dev Teams - Licensed under GNU GPL
 // For more information, see COPYING in the main folder
 
-#include "storage.hpp"
-
 #include <cstdlib>
 #include <cstring>
 
-#include "../common/db.hpp"
-#include "../common/nullpo.hpp"
+#include "../compat/nullpo.hpp"
+
+#include "../generic/db.hpp"
 
 #include "chrif.hpp"
 #include "clif.hpp"
diff --git a/src/map/storage.hpp b/src/map/storage.hpp
index 76be7d6..0e6d111 100644
--- a/src/map/storage.hpp
+++ b/src/map/storage.hpp
@@ -1,9 +1,9 @@
+#ifndef TMWA_MAP_STORAGE_HPP
+#define TMWA_MAP_STORAGE_HPP
+
 // Copyright (c) Athena Dev Teams - Licensed under GNU GPL
 // For more information, see COPYING in the main folder
 
-#ifndef STORAGE_HPP
-#define STORAGE_HPP
-
 # include "map.hpp"
 
 int storage_storageopen(dumb_ptr<map_session_data> sd);
@@ -17,4 +17,4 @@ int storage_storage_quit(dumb_ptr<map_session_data> sd);
 int storage_storage_save(int account_id, int final);
 int storage_storage_saved(int account_id);
 
-#endif // STORAGE_HPP
+#endif // TMWA_MAP_STORAGE_HPP
diff --git a/src/map/tmw.cpp b/src/map/tmw.cpp
index 16276fa..1c18bd5 100644
--- a/src/map/tmw.cpp
+++ b/src/map/tmw.cpp
@@ -3,14 +3,14 @@
 #include <cctype>
 #include <cstring>
 
+#include "../compat/nullpo.hpp"
+
 #include "../strings/astring.hpp"
 #include "../strings/zstring.hpp"
 #include "../strings/xstring.hpp"
 
 #include "../io/cxxstdio.hpp"
 
-#include "../common/nullpo.hpp"
-
 #include "atcommand.hpp"
 #include "battle.hpp"
 #include "chrif.hpp"
diff --git a/src/map/tmw.hpp b/src/map/tmw.hpp
index e90143d..65e71fb 100644
--- a/src/map/tmw.hpp
+++ b/src/map/tmw.hpp
@@ -1,14 +1,15 @@
-#ifndef TMW_HPP
-#define TMW_HPP
+#ifndef TMWA_MAP_TMW_HPP
+#define TMWA_MAP_TMW_HPP
 
 # include "../strings/fwd.hpp"
 
-# include "../common/const_array.hpp"
-# include "../common/dumb_ptr.hpp"
+# include "../generic/const_array.hpp"
+
+# include "../mmo/dumb_ptr.hpp"
 
 # include "map.hpp"
 
 int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message);
 void tmw_GmHackMsg(ZString line);
 
-#endif // TMW_HPP
+#endif // TMWA_MAP_TMW_HPP
diff --git a/src/map/trade.cpp b/src/map/trade.cpp
index 9626191..e9c5fd4 100644
--- a/src/map/trade.cpp
+++ b/src/map/trade.cpp
@@ -1,8 +1,8 @@
 #include "trade.hpp"
 
-#include "../io/cxxstdio.hpp"
+#include "../compat/nullpo.hpp"
 
-#include "../common/nullpo.hpp"
+#include "../io/cxxstdio.hpp"
 
 #include "battle.hpp"
 #include "clif.hpp"
diff --git a/src/map/trade.hpp b/src/map/trade.hpp
index e50d608..1fb30f4 100644
--- a/src/map/trade.hpp
+++ b/src/map/trade.hpp
@@ -1,5 +1,5 @@
-#ifndef TRADE_HPP
-#define TRADE_HPP
+#ifndef TMWA_MAP_TRADE_HPP
+#define TMWA_MAP_TRADE_HPP
 
 # include "map.hpp"
 
@@ -11,4 +11,4 @@ void trade_tradecancel(dumb_ptr<map_session_data> sd);
 void trade_tradecommit(dumb_ptr<map_session_data> sd);
 void trade_verifyzeny(dumb_ptr<map_session_data> sd);
 
-#endif // TRADE_HPP
+#endif // TMWA_MAP_TRADE_HPP
diff --git a/src/mmo/config_parse.cpp b/src/mmo/config_parse.cpp
new file mode 100644
index 0000000..d677d8e
--- /dev/null
+++ b/src/mmo/config_parse.cpp
@@ -0,0 +1,154 @@
+#include "config_parse.hpp"
+//    config_parse.hpp - Framework for per-server config parsers.
+//
+//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+#include "../strings/xstring.hpp"
+#include "../strings/zstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/line.hpp"
+
+#include "version.hpp"
+
+#include "../poison.hpp"
+
+bool is_comment(XString line)
+{
+    return not line or line.startswith("//");
+}
+
+template<class ZS>
+inline
+bool config_split_impl(ZS line, XString *key, ZS *value)
+{
+    // unconditionally fail if line contains control characters
+    if (std::find_if(line.begin(), line.end(),
+                [](unsigned char c) { return c < ' '; }
+                ) != line.end())
+        return false;
+    // try to find a colon, fail if not found
+    typename ZS::iterator colon = std::find(line.begin(), line.end(), ':');
+    if (colon == line.end())
+        return false;
+
+    *key = line.xislice_h(colon).strip();
+    // move past the colon and any spaces
+    ++colon;
+    *value = line.xislice_t(colon).lstrip();
+    return true;
+}
+
+// eventually this should go away
+bool config_split(ZString line, XString *key, ZString *value)
+{
+    return config_split_impl(line, key, value);
+}
+// and use this instead
+bool config_split(XString line, XString *key, XString *value)
+{
+    return config_split_impl(line, key, value);
+}
+
+/// Master config parser. This handles 'import' and 'version-ge' etc.
+/// Then it defers to the inferior parser for a line it does not understand.
+bool load_config_file(ZString filename, ConfigItemParser slave)
+{
+    io::LineReader in(filename);
+    if (!in.is_open())
+    {
+        PRINTF("Unable to open file: %s\n", filename);
+        return false;
+    }
+    io::Line line;
+    bool rv = true;
+    while (in.read_line(line))
+    {
+        if (is_comment(line.text))
+            continue;
+        XString key;
+        ZString value;
+        if (!config_split(line.text, &key, &value))
+        {
+            line.error("Bad config line");
+            rv = false;
+            continue;
+        }
+        if (key == "import")
+        {
+            rv &= load_config_file(value, slave);
+            continue;
+        }
+        else if (key == "version-lt")
+        {
+            Version vers;
+            if (!extract(value, &vers))
+            {
+                rv = false;
+                continue;
+            }
+            if (CURRENT_VERSION < vers)
+                continue;
+            break;
+        }
+        else if (key == "version-le")
+        {
+            Version vers;
+            if (!extract(value, &vers))
+            {
+                rv = false;
+                continue;
+            }
+            if (CURRENT_VERSION <= vers)
+                continue;
+            break;
+        }
+        else if (key == "version-gt")
+        {
+            Version vers;
+            if (!extract(value, &vers))
+            {
+                rv = false;
+                continue;
+            }
+            if (CURRENT_VERSION > vers)
+                continue;
+            break;
+        }
+        else if (key == "version-ge")
+        {
+            Version vers;
+            if (!extract(value, &vers))
+            {
+                rv = false;
+                continue;
+            }
+            if (CURRENT_VERSION >= vers)
+                continue;
+            break;
+        }
+        else if (!slave(key, value))
+        {
+            line.error("Bad config key or value");
+            rv = false;
+            continue;
+        }
+        // nothing to see here, move along
+    }
+    return rv;
+}
diff --git a/src/mmo/config_parse.hpp b/src/mmo/config_parse.hpp
new file mode 100644
index 0000000..dd1b79e
--- /dev/null
+++ b/src/mmo/config_parse.hpp
@@ -0,0 +1,36 @@
+#ifndef TMWA_MMO_CONFIG_PARSE_HPP
+#define TMWA_MMO_CONFIG_PARSE_HPP
+//    config_parse.hpp - Framework for per-server config parsers.
+//
+//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include "../strings/fwd.hpp"
+
+typedef bool (*ConfigItemParser)(XString key, ZString value);
+
+bool is_comment(XString line);
+bool config_split(ZString line, XString *key, ZString *value);
+bool config_split(XString line, XString *key, XString *value);
+
+/// Master config parser. This handles 'import' and 'version-ge' etc.
+/// Then it defers to the inferior parser for a line it does not understand.
+bool load_config_file(ZString filename, ConfigItemParser slave);
+
+#endif // TMWA_MMO_CONFIG_PARSE_HPP
diff --git a/src/mmo/core.cpp b/src/mmo/core.cpp
new file mode 100644
index 0000000..1a9a52e
--- /dev/null
+++ b/src/mmo/core.cpp
@@ -0,0 +1,125 @@
+#include "core.hpp"
+
+#include <sys/wait.h>
+
+#include <unistd.h>
+
+#include <csignal>
+#include <cstdlib>
+#include <ctime>
+
+#include "../strings/zstring.hpp"
+
+#include "../generic/random.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "socket.hpp"
+#include "timer.hpp"
+
+#include "../poison.hpp"
+
+// Added by Gabuzomeu
+//
+// This is an implementation of signal() using sigaction() for portability.
+// (sigaction() is POSIX; signal() is not.)  Taken from Stevens' _Advanced
+// Programming in the UNIX Environment_.
+//
+typedef void(*sigfunc)(int);
+static
+sigfunc compat_signal(int signo, sigfunc func)
+{
+    struct sigaction sact, oact;
+
+    sact.sa_handler = func;
+    sigfillset(&sact.sa_mask);
+    sigdelset(&sact.sa_mask, SIGSEGV);
+    sigdelset(&sact.sa_mask, SIGBUS);
+    sigdelset(&sact.sa_mask, SIGTRAP);
+    sigdelset(&sact.sa_mask, SIGILL);
+    sigdelset(&sact.sa_mask, SIGFPE);
+    sact.sa_flags = 0;
+
+    if (sigaction(signo, &sact, &oact) < 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+        return SIG_ERR;
+#pragma GCC diagnostic pop
+
+    return oact.sa_handler;
+}
+
+volatile
+bool runflag = true;
+
+static
+void chld_proc(int)
+{
+    wait(NULL);
+}
+static
+void sig_proc(int)
+{
+    for (int i = 1; i < 31; ++i)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+        compat_signal(i, SIG_IGN);
+#pragma GCC diagnostic pop
+    runflag = false;
+}
+
+/*
+    Note about fatal signals:
+
+    Under certain circumstances,
+    the following signals MUST not be ignored:
+    SIGFPE, SIGSEGV, SIGILL
+    Unless you use SA_SIGINFO and *carefully* check the origin,
+    that means they must be SIG_DFL.
+ */
+int main(int argc, char **argv)
+{
+    // ZString args[argc]; is (deliberately!) not supported by clang yet
+    ZString *args = static_cast<ZString *>(alloca(argc * sizeof(ZString)));
+    for (int i = 0; i < argc; ++i)
+        args[i] = ZString(strings::really_construct_from_a_pointer, argv[i], nullptr);
+    do_init(argc, args);
+
+    if (!runflag)
+    {
+        PRINTF("Fatal error during startup; exiting\n");
+        return 1;
+    }
+    // set up exit handlers *after* the initialization has happened.
+    // This is because term_func is likely to depend on successful init.
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+    compat_signal(SIGPIPE, SIG_IGN);
+#pragma GCC diagnostic pop
+    compat_signal(SIGTERM, sig_proc);
+    compat_signal(SIGINT, sig_proc);
+    compat_signal(SIGCHLD, chld_proc);
+
+    // Signal to create coredumps by system when necessary (crash)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+    compat_signal(SIGSEGV, SIG_DFL);
+    compat_signal(SIGBUS, SIG_DFL);
+    compat_signal(SIGTRAP, SIG_DFL);
+    compat_signal(SIGILL, SIG_DFL);
+    compat_signal(SIGFPE, SIG_DFL);
+#pragma GCC diagnostic pop
+
+    atexit(term_func);
+
+    while (runflag)
+    {
+        // TODO - if timers take a long time to run, this
+        // may wait too long in sendrecv
+        tick_t now = milli_clock::now();
+        interval_t next = do_timer(now);
+        do_sendrecv(next);
+        do_parsepacket();
+    }
+}
diff --git a/src/mmo/core.hpp b/src/mmo/core.hpp
new file mode 100644
index 0000000..1788ece
--- /dev/null
+++ b/src/mmo/core.hpp
@@ -0,0 +1,22 @@
+#ifndef TMWA_MMO_CORE_HPP
+#define TMWA_MMO_CORE_HPP
+
+# include "../sanity.hpp"
+
+# include "../strings/fwd.hpp"
+
+/// core.c contains a server-independent main() function
+/// and then runs a do_sendrecv loop
+
+/// When this is cleared, the server exits gracefully.
+extern volatile bool runflag;
+
+/// This is an external function defined by each server
+/// This function must register stuff for the parse loop
+extern int do_init(int, ZString *);
+
+/// Cleanup function called whenever a signal kills us
+/// or when if we manage to exit() gracefully.
+extern void term_func(void);
+
+#endif // TMWA_MMO_CORE_HPP
diff --git a/src/mmo/dumb_ptr.cpp b/src/mmo/dumb_ptr.cpp
new file mode 100644
index 0000000..5f73d27
--- /dev/null
+++ b/src/mmo/dumb_ptr.cpp
@@ -0,0 +1,3 @@
+#include "dumb_ptr.hpp"
+
+#include "../poison.hpp"
diff --git a/src/mmo/dumb_ptr.hpp b/src/mmo/dumb_ptr.hpp
new file mode 100644
index 0000000..98c6308
--- /dev/null
+++ b/src/mmo/dumb_ptr.hpp
@@ -0,0 +1,276 @@
+#ifndef TMWA_MMO_DUMB_PTR_HPP
+#define TMWA_MMO_DUMB_PTR_HPP
+//    ptr.hpp - temporary new/delete wrappers
+//
+//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include <cstring>
+
+# include <algorithm>
+
+# include "../strings/astring.hpp"
+# include "../strings/zstring.hpp"
+# include "../strings/xstring.hpp"
+
+# include "../generic/const_array.hpp"
+
+// unmanaged new/delete-able pointer
+// should be replaced by std::unique_ptr<T>
+template<class T>
+class dumb_ptr
+{
+    template<class U>
+    friend class dumb_ptr;
+    T *impl;
+public:
+    explicit
+    dumb_ptr(T *p=nullptr)
+    : impl(p)
+    {}
+    template<class U>
+    dumb_ptr(dumb_ptr<U> p)
+    : impl(p.impl)
+    {}
+    dumb_ptr(std::nullptr_t)
+    : impl(nullptr)
+    {}
+
+    void delete_()
+    {
+        delete impl;
+        *this = nullptr;
+    }
+    template<class... A>
+    void new_(A&&... a)
+    {
+        impl = new T(std::forward<A>(a)...);
+    }
+    template<class... A>
+    static
+    dumb_ptr<T> make(A&&... a)
+    {
+        return dumb_ptr<T>(new T(std::forward<A>(a)...));
+    }
+    dumb_ptr& operator = (std::nullptr_t)
+    {
+        impl = nullptr;
+        return *this;
+    }
+
+    T& operator *() const
+    {
+        return *impl;
+    }
+    T *operator->() const
+    {
+        return impl;
+    }
+
+    explicit
+    operator bool() const
+    {
+        return impl;
+    }
+    bool operator !() const
+    {
+        return !impl;
+    }
+
+    friend bool operator == (dumb_ptr l, dumb_ptr r)
+    {
+        return l.impl == r.impl;
+    }
+    friend bool operator != (dumb_ptr l, dumb_ptr r)
+    {
+        return !(l == r);
+    }
+};
+
+// unmanaged new/delete-able pointer
+// should be replaced by std::unique_ptr<T[]> or std::vector<T>
+template<class T>
+class dumb_ptr<T[]>
+{
+    T *impl;
+    size_t sz;
+public:
+    dumb_ptr() : impl(), sz() {}
+    dumb_ptr(std::nullptr_t)
+    : impl(nullptr), sz(0) {}
+    dumb_ptr(T *p, size_t z)
+    : impl(p)
+    , sz(z)
+    {}
+
+    void delete_()
+    {
+        delete[] impl;
+        *this = nullptr;
+    }
+    void new_(size_t z)
+    {
+        impl = new T[z]();
+        sz = z;
+    }
+    static
+    dumb_ptr<T[]> make(size_t z)
+    {
+        return dumb_ptr<T[]>(new T[z](), z);
+    }
+    dumb_ptr& operator = (std::nullptr_t)
+    {
+        impl = nullptr;
+        sz = 0;
+        return *this;
+    }
+
+    size_t size() const
+    {
+        return sz;
+    }
+    void resize(size_t z)
+    {
+        if (z == sz)
+            return;
+        T *np = new T[z]();
+        // not exception-safe, but we don't have a dtor anyway
+        size_t i = std::min(z, sz);
+        while (i-->0)
+            np[i] = std::move(impl[i]);
+        delete[] impl;
+        impl = np;
+        sz = z;
+    }
+
+    T& operator[](size_t i) const
+    {
+        return impl[i];
+    }
+
+    explicit
+    operator bool() const
+    {
+        return impl;
+    }
+    bool operator !() const
+    {
+        return !impl;
+    }
+
+    friend bool operator == (dumb_ptr l, dumb_ptr r)
+    {
+        return l.impl == r.impl;
+    }
+    friend bool operator != (dumb_ptr l, dumb_ptr r)
+    {
+        return !(l == r);
+    }
+};
+
+struct dumb_string
+{
+    dumb_ptr<char[]> impl;
+
+    dumb_string()
+    : impl()
+    {}
+    dumb_string(char *) = delete;
+    // copy ctor, copy assign, and dtor are all default
+
+    static dumb_string copy(const char *b, const char *e)
+    {
+        dumb_string rv;
+        rv.impl.new_((e - b) + 1);
+        std::copy(b, e, &rv.impl[0]);
+        return rv;
+    }
+    static dumb_string copy(const char *sz)
+    {
+        return dumb_string::copy(sz, sz + strlen(sz));
+    }
+    static dumb_string copys(XString s)
+    {
+        return dumb_string::copy(&*s.begin(), &*s.end());
+    }
+    static
+# ifndef __clang__
+    __attribute__((warning("shouldn't use this - slice instead")))
+# endif
+    dumb_string copyn(const char *sn, size_t n)
+    {
+        return dumb_string::copy(sn, sn + strnlen(sn, n));
+    }
+
+    static
+    dumb_string fake(ZString p)
+    {
+        dumb_string rv;
+        size_t len = p.size();
+        rv.impl = dumb_ptr<char[]>(const_cast<char *>(p.c_str()), len);
+        return rv;
+    }
+
+    dumb_string dup() const
+    {
+        return dumb_string::copy(&impl[0]);
+    }
+    void delete_()
+    {
+        impl.delete_();
+    }
+
+    const char *c_str() const
+    {
+        return &impl[0];
+    }
+
+    operator ZString() const
+    {
+        return ZString(strings::really_construct_from_a_pointer, c_str(), nullptr);
+    }
+
+    AString str() const
+    {
+        return ZString(*this);
+    }
+
+    char& operator[](size_t i) const
+    {
+        return impl[i];
+    }
+
+    explicit
+    operator bool() const
+    {
+        return bool(impl);
+    }
+    bool operator !() const
+    {
+        return !impl;
+    }
+};
+
+inline
+const char *convert_for_printf(dumb_string ds)
+{
+    return ds.c_str();
+}
+
+#endif // TMWA_MMO_DUMB_PTR_HPP
diff --git a/src/mmo/extract.cpp b/src/mmo/extract.cpp
new file mode 100644
index 0000000..378986d
--- /dev/null
+++ b/src/mmo/extract.cpp
@@ -0,0 +1,62 @@
+#include "extract.hpp"
+//    extract.cpp - a simple, hierarchical, tokenizer
+//
+//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+#include "../strings/astring.hpp"
+#include "../strings/xstring.hpp"
+#include "../strings/vstring.hpp"
+
+#include "../poison.hpp"
+
+bool extract(XString str, XString *rv)
+{
+    *rv = str;
+    return true;
+}
+
+bool extract(XString str, AString *rv)
+{
+    *rv = str;
+    return true;
+}
+
+bool extract(XString str, struct global_reg *var)
+{
+    return extract(str,
+            record<','>(&var->str, &var->value));
+}
+
+bool extract(XString str, struct item *it)
+{
+    XString ignored;
+    return extract(str,
+            record<',', 11>(
+                &it->id,
+                &it->nameid,
+                &it->amount,
+                &it->equip,
+                &ignored,
+                &ignored,
+                &ignored,
+                &ignored,
+                &ignored,
+                &ignored,
+                &ignored,
+                &ignored));
+}
diff --git a/src/mmo/extract.hpp b/src/mmo/extract.hpp
new file mode 100644
index 0000000..0ea9eb9
--- /dev/null
+++ b/src/mmo/extract.hpp
@@ -0,0 +1,224 @@
+#ifndef TMWA_MMO_EXTRACT_HPP
+#define TMWA_MMO_EXTRACT_HPP
+//    extract.hpp - a simple, hierarchical, tokenizer
+//
+//    Copyright © 2012-2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include <algorithm>
+
+# include "../strings/xstring.hpp"
+
+# include "../generic/const_array.hpp"
+
+# include "mmo.hpp"
+# include "utils.hpp"
+
+template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && !std::is_same<T, bool>::value>::type>
+bool extract(XString str, T *iv)
+{
+    if (!str || str.size() > 20)
+        return false;
+    if (!((str.front() == '-' && std::is_signed<T>::value)
+            || ('0' <= str.front() && str.front() <= '9')))
+        return false;
+    // needs a NUL, but can't always be given one. TODO VString?
+    char buf[20 + 1];
+    std::copy(str.begin(), str.end(), buf);
+    buf[str.size()] = '\0';
+
+    char *end;
+    errno = 0;
+    if (std::is_signed<T>::value)
+    {
+        long long v = strtoll(buf, &end, 10);
+        if (errno || *end)
+            return false;
+        *iv = v;
+        return *iv == v;
+    }
+    else
+    {
+        unsigned long long v = strtoull(buf, &end, 10);
+        if (errno || *end)
+            return false;
+        *iv = v;
+        return *iv == v;
+    }
+}
+
+inline
+bool extract(XString str, TimeT *tv)
+{
+    return extract(str, &tv->value);
+}
+
+// extra typename=void to workaround some duplicate overload rule
+template<class T, typename=typename std::enable_if<std::is_enum<T>::value>::type, typename=void>
+bool extract(XString str, T *iv)
+{
+    typedef typename underlying_type<T>::type U;
+    U v;
+    // defer to integer version
+    if (!extract(str, &v))
+        return false;
+    // TODO check bounds using enum min/max as in SSCANF
+    *iv = static_cast<T>(v);
+    return true;
+}
+
+bool extract(XString str, XString *rv);
+
+bool extract(XString str, AString *rv);
+
+template<uint8_t N>
+bool extract(XString str, VString<N> *out)
+{
+    if (str.size() > N)
+        return false;
+    *out = str;
+    return true;
+}
+
+template<class T>
+class LStripper
+{
+public:
+    T impl;
+};
+
+template<class T>
+LStripper<T> lstripping(T v)
+{
+    return {v};
+}
+
+template<class T>
+bool extract(XString str, LStripper<T> out)
+{
+    return extract(str.lstrip(), out.impl);
+}
+
+// basically just a std::tuple
+// but it provides its data members publically
+template<char split, int n, class... T>
+class Record;
+template<char split, int n>
+class Record<split, n>
+{
+};
+template<char split, int n, class F, class... R>
+class Record<split, n, F, R...>
+{
+public:
+    F frist;
+    Record<split, n - 1, R...> rest;
+public:
+    Record(F f, R... r)
+    : frist(f), rest(r...)
+    {}
+};
+template<char split, class... T>
+Record<split, sizeof...(T), T...> record(T... t)
+{
+    return Record<split, sizeof...(T), T...>(t...);
+}
+template<char split, int n, class... T>
+Record<split, n, T...> record(T... t)
+{
+    static_assert(0 < n && n < sizeof...(T), "don't be silly");
+    return Record<split, n, T...>(t...);
+}
+
+template<char split, int n>
+bool extract(XString str, Record<split, n>)
+{
+    return !str;
+}
+
+template<char split, int n, class F, class... R>
+bool extract(XString str, Record<split, n, F, R...> rec)
+{
+    XString::iterator s = std::find(str.begin(), str.end(), split);
+    XString::iterator s2 = s;
+    if (s2 != str.end())
+        ++s2;
+    XString head = str.xislice_h(s);
+    XString tail = str.xislice_t(s2);
+    if (s == str.end())
+        return (extract(head, rec.frist) && n <= 1)
+            || (!head && n <= 0);
+
+    return (extract(head, rec.frist) || n <= 0)
+        && extract(tail, rec.rest);
+}
+
+template<char split, class T>
+struct VRecord
+{
+    std::vector<T> *arr;
+};
+
+template<char split, class T>
+VRecord<split, T> vrec(std::vector<T> *arr)
+{
+    return {arr};
+}
+
+template<char split, class T>
+bool extract(XString str, VRecord<split, T> rec)
+{
+    if (!str)
+        return true;
+    XString::iterator s = std::find(str.begin(), str.end(), split);
+    rec.arr->emplace_back();
+    if (s == str.end())
+        return extract(str, &rec.arr->back());
+    return extract(str.xislice_h(s), &rec.arr->back())
+        && extract(str.xislice_t(s + 1), rec);
+}
+
+bool extract(XString str, struct global_reg *var);
+
+bool extract(XString str, struct item *it);
+
+inline
+bool extract(XString str, MapName *m)
+{
+    XString::iterator it = std::find(str.begin(), str.end(), '.');
+    str = str.xislice_h(it);
+    VString<15> tmp;
+    bool rv = extract(str, &tmp);
+    *m = tmp;
+    return rv;
+}
+
+inline
+bool extract(XString str, CharName *out)
+{
+    VString<23> tmp;
+    if (extract(str, &tmp))
+    {
+        *out = CharName(tmp);
+        return true;
+    }
+    return false;
+}
+
+#endif // TMWA_MMO_EXTRACT_HPP
diff --git a/src/mmo/extract_test.cpp b/src/mmo/extract_test.cpp
new file mode 100644
index 0000000..3d0610e
--- /dev/null
+++ b/src/mmo/extract_test.cpp
@@ -0,0 +1,334 @@
+#include "extract.hpp"
+
+#include <gtest/gtest.h>
+
+#include "../strings/xstring.hpp"
+
+TEST(extract, record_int)
+{
+    int x, y, z;
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 2 3 4 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 2 3 4", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 3 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 3", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 2 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 2", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract(" ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(0, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("", record<' '>(&x, &y, &z)));
+    EXPECT_EQ(0, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+
+    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 2 3 4", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 3 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 3", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract(" ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(0, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ(0, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+
+    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("1 2 3 4", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 3 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 3", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(3, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 2", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(2, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_TRUE(extract("1", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(1, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract(" ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(0, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+    EXPECT_FALSE(extract("", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ(0, x);
+    EXPECT_EQ(0, y);
+    EXPECT_EQ(0, z);
+    x = y = z = 0;
+}
+
+TEST(extract, record_str)
+{
+    XString x, y, z;
+    x = y = z = "";
+    EXPECT_FALSE(extract("1 2 3 4 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1 2 3 4", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 3 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 3", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1 2", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1 ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract(" ", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("", record<' '>(&x, &y, &z)));
+    EXPECT_EQ("", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+
+    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1 2 3 4", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 3 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 3", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract(" ", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("", record<' ', 2>(&x, &y, &z)));
+    EXPECT_EQ("", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+
+    EXPECT_FALSE(extract("1 2 3 4 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_FALSE(extract("1 2 3 4", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 3 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 3", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("3", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 2", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("2", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1 ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("1", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("1", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract(" ", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+    EXPECT_TRUE(extract("", record<' ', 1>(&x, &y, &z)));
+    EXPECT_EQ("", x);
+    EXPECT_EQ("", y);
+    EXPECT_EQ("", z);
+    x = y = z = "";
+}
+
+TEST(extract, mapname)
+{
+    MapName map;
+    EXPECT_TRUE(extract("abc", &map));
+    EXPECT_EQ(map, "abc");
+    EXPECT_TRUE(extract("abc.gat", &map));
+    EXPECT_EQ(map, "abc");
+    EXPECT_TRUE(extract("abcdefghijklmno", &map));
+    EXPECT_EQ(map, "abcdefghijklmno");
+    EXPECT_TRUE(extract("abcdefghijklmno.gat", &map));
+    EXPECT_EQ(map, "abcdefghijklmno");
+}
diff --git a/src/mmo/human_time_diff.cpp b/src/mmo/human_time_diff.cpp
new file mode 100644
index 0000000..93a6d52
--- /dev/null
+++ b/src/mmo/human_time_diff.cpp
@@ -0,0 +1,3 @@
+#include "human_time_diff.hpp"
+
+#include "../poison.hpp"
diff --git a/src/mmo/human_time_diff.hpp b/src/mmo/human_time_diff.hpp
new file mode 100644
index 0000000..689b8d9
--- /dev/null
+++ b/src/mmo/human_time_diff.hpp
@@ -0,0 +1,86 @@
+#ifndef TMWA_MMO_HUMAN_TIME_DIFF_HPP
+#define TMWA_MMO_HUMAN_TIME_DIFF_HPP
+//    human_time_diff.hpp - broken deltas
+//
+//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include "../strings/xstring.hpp"
+
+# include "extract.hpp"
+
+struct HumanTimeDiff
+{
+    short year, month, day, hour, minute, second;
+
+    explicit
+    operator bool()
+    {
+        return year || month || day || hour || minute || second;
+    }
+
+    bool operator !()
+    {
+        return !bool(*this);
+    }
+};
+
+inline
+bool extract(XString str, HumanTimeDiff *iv)
+{
+    // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s)
+    // there are NO spaces here
+    // parse by counting the number starts
+    auto is_num = [](char c)
+    { return c == '-' || c == '+' || ('0' <= c && c <= '9'); };
+    if (!str || !is_num(str.front()))
+        return false;
+    *iv = HumanTimeDiff{};
+    while (str)
+    {
+        auto it = std::find_if_not(str.begin(), str.end(), is_num);
+        auto it2 = std::find_if(it, str.end(), is_num);
+        XString number = str.xislice_h(it);
+        XString suffix = str.xislice(it, it2);
+        str = str.xislice_t(it2);
+
+        short *ptr = nullptr;
+        if (suffix == "y" || suffix == "a")
+            ptr = &iv->year;
+        else if (suffix == "m")
+            ptr = &iv->month;
+        else if (suffix == "j" || suffix == "d")
+            ptr = &iv->day;
+        else if (suffix == "h")
+            ptr = &iv->hour;
+        else if (suffix == "mn")
+            ptr = &iv->minute;
+        else if (suffix == "s")
+            ptr = &iv->second;
+        else
+            return false;
+        if (number.startswith('+') && !number.startswith("+-"))
+            number = number.xslice_t(1);
+        if (*ptr || !extract(number, ptr))
+            return false;
+    }
+    return true;
+}
+
+#endif // TMWA_MMO_HUMAN_TIME_DIFF_HPP
diff --git a/src/mmo/human_time_diff_test.cpp b/src/mmo/human_time_diff_test.cpp
new file mode 100644
index 0000000..d3ddad1
--- /dev/null
+++ b/src/mmo/human_time_diff_test.cpp
@@ -0,0 +1,83 @@
+#include "human_time_diff.hpp"
+
+#include <gtest/gtest.h>
+
+// a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s)
+
+TEST(humantimediff, single)
+{
+    HumanTimeDiff diff;
+
+    EXPECT_TRUE(extract("42y", &diff));
+    EXPECT_EQ(42, diff.year);
+    EXPECT_EQ(0, diff.month);
+    EXPECT_EQ(0, diff.day);
+    EXPECT_EQ(0, diff.hour);
+    EXPECT_EQ(0, diff.minute);
+    EXPECT_EQ(0, diff.second);
+
+    EXPECT_TRUE(extract("42m", &diff));
+    EXPECT_EQ(0, diff.year);
+    EXPECT_EQ(42, diff.month);
+    EXPECT_EQ(0, diff.day);
+    EXPECT_EQ(0, diff.hour);
+    EXPECT_EQ(0, diff.minute);
+    EXPECT_EQ(0, diff.second);
+
+    EXPECT_TRUE(extract("42d", &diff));
+    EXPECT_EQ(0, diff.year);
+    EXPECT_EQ(0, diff.month);
+    EXPECT_EQ(42, diff.day);
+    EXPECT_EQ(0, diff.hour);
+    EXPECT_EQ(0, diff.minute);
+    EXPECT_EQ(0, diff.second);
+
+    EXPECT_TRUE(extract("42h", &diff));
+    EXPECT_EQ(0, diff.year);
+    EXPECT_EQ(0, diff.month);
+    EXPECT_EQ(0, diff.day);
+    EXPECT_EQ(42, diff.hour);
+    EXPECT_EQ(0, diff.minute);
+    EXPECT_EQ(0, diff.second);
+
+    EXPECT_TRUE(extract("42mn", &diff));
+    EXPECT_EQ(0, diff.year);
+    EXPECT_EQ(0, diff.month);
+    EXPECT_EQ(0, diff.day);
+    EXPECT_EQ(0, diff.hour);
+    EXPECT_EQ(42, diff.minute);
+    EXPECT_EQ(0, diff.second);
+
+    EXPECT_TRUE(extract("42s", &diff));
+    EXPECT_EQ(0, diff.year);
+    EXPECT_EQ(0, diff.month);
+    EXPECT_EQ(0, diff.day);
+    EXPECT_EQ(0, diff.hour);
+    EXPECT_EQ(0, diff.minute);
+    EXPECT_EQ(42, diff.second);
+
+    EXPECT_TRUE(extract("+42y", &diff));
+    EXPECT_EQ(42, diff.year);
+    EXPECT_TRUE(extract("-42y", &diff));
+    EXPECT_EQ(-42, diff.year);
+    EXPECT_FALSE(extract("++42y", &diff));
+    EXPECT_FALSE(extract("+-42y", &diff));
+    EXPECT_FALSE(extract("-+42y", &diff));
+    EXPECT_FALSE(extract("--42y", &diff));
+    EXPECT_FALSE(extract("4+2y", &diff));
+    EXPECT_FALSE(extract("42z", &diff));
+}
+
+TEST(humantimediff, multiple)
+{
+    HumanTimeDiff diff;
+
+    EXPECT_TRUE(extract("42y23m-2d", &diff));
+    EXPECT_EQ(42, diff.year);
+    EXPECT_EQ(23, diff.month);
+    EXPECT_EQ(-2, diff.day);
+    EXPECT_EQ(0, diff.hour);
+    EXPECT_EQ(0, diff.minute);
+    EXPECT_EQ(0, diff.second);
+    EXPECT_FALSE(extract("1y2y", &diff));
+}
diff --git a/src/mmo/ip.cpp b/src/mmo/ip.cpp
new file mode 100644
index 0000000..146734a
--- /dev/null
+++ b/src/mmo/ip.cpp
@@ -0,0 +1,114 @@
+#include "ip.hpp"
+//    ip.cpp - Implementation of IP address functions.
+//
+//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+#include "../strings/xstring.hpp"
+#include "../strings/vstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "../poison.hpp"
+
+bool extract(XString str, IP4Address *rv)
+{
+    if (str.endswith('.'))
+        return false;
+    uint8_t buf[4];
+    if (extract(str, record<'.'>(&buf[0], &buf[1], &buf[2], &buf[3])))
+    {
+        *rv = IP4Address(buf);
+        return true;
+    }
+    return false;
+}
+
+bool extract(XString str, IP4Mask *rv)
+{
+    IP4Address a, m;
+    unsigned b;
+    XString l, r;
+    if (str.endswith('/'))
+        return false;
+    if (extract(str, record<'/'>(&l, &r)))
+    {
+        // a.b.c.d/e.f.g.h or a.b.c.d/n
+        if (!extract(l, &a))
+            return false;
+        if (extract(r, &m))
+        {
+            *rv = IP4Mask(a, m);
+            return true;
+        }
+        if (!extract(r, &b) || b > 32)
+            return false;
+    }
+    else
+    {
+        // a.b.c.d or a.b.c.d. or a.b.c. or a.b. or a.
+        if (extract(str, &a))
+        {
+            *rv = IP4Mask(a, IP4_BROADCAST);
+            return true;
+        }
+        if (!str.endswith('.'))
+            return false;
+        uint8_t d[4] {};
+        if (extract(str, record<'.'>(&d[0], &d[1], &d[2], &d[3])))
+            b = 32;
+        else if (extract(str, record<'.'>(&d[0], &d[1], &d[2])))
+            b = 24;
+        else if (extract(str, record<'.'>(&d[0], &d[1])))
+            b = 16;
+        else if (extract(str, record<'.'>(&d[0])))
+            b = 8;
+        else
+            return false;
+        a = IP4Address(d);
+    }
+    // a is set; need to construct m from b
+    if (b == 0)
+        m = IP4Address();
+    else if (b == 32)
+        m = IP4_BROADCAST;
+    else
+    {
+        uint32_t s = -1;
+        s <<= (32 - b);
+        m = IP4Address({
+                static_cast<uint8_t>(s >> 24),
+                static_cast<uint8_t>(s >> 16),
+                static_cast<uint8_t>(s >> 8),
+                static_cast<uint8_t>(s >> 0),
+        });
+    }
+    *rv = IP4Mask(a, m);
+    return true;
+}
+
+VString<15> convert_for_printf(IP4Address a_)
+{
+    const uint8_t *a = a_.bytes();
+    return STRNPRINTF(16, "%hhu.%hhu.%hhu.%hhu", a[0], a[1], a[2], a[3]);
+}
+
+VString<31> convert_for_printf(IP4Mask a)
+{
+    return STRNPRINTF(32, "%s/%s",
+            a.addr(), a.mask());
+}
diff --git a/src/mmo/ip.hpp b/src/mmo/ip.hpp
new file mode 100644
index 0000000..a425710
--- /dev/null
+++ b/src/mmo/ip.hpp
@@ -0,0 +1,164 @@
+#ifndef TMWA_MMO_IP_HPP
+#define TMWA_MMO_IP_HPP
+//    ip.hpp - classes to deal with IP addresses.
+//
+//    Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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/>.
+
+# include "../sanity.hpp"
+
+# include <netinet/in.h>
+
+# include "../strings/fwd.hpp"
+
+# include "extract.hpp"
+
+// TODO - in the long run ports belong here also
+// and of course, IPv6 stuff.
+// But what about unix socket addresses?
+
+/// Helper function
+template<class T, size_t n>
+constexpr
+bool _ce_a_lt(T (&a)[n], T (&b)[n], size_t i=0)
+{
+    return (i != n
+            && (a[i] < b[i]
+                || (a[i] == b[i]
+                    && _ce_a_lt(a, b, i + 1))));
+}
+
+/// A 32-bit Ipv4 address. Does not include a port.
+/// Guaranteed to be laid out like the network wants.
+class IP4Address
+{
+    uint8_t _addr[4];
+public:
+    constexpr
+    IP4Address()
+    : _addr{}
+    {}
+    constexpr explicit
+    IP4Address(const uint8_t (&a)[4])
+    : _addr{a[0], a[1], a[2], a[3]}
+    {}
+    explicit
+    IP4Address(struct in_addr addr)
+    {
+        static_assert(sizeof(addr) == sizeof(_addr), "4 bytes");
+        *this = IP4Address(reinterpret_cast<const uint8_t (&)[4]>(addr));
+    }
+    explicit
+    operator struct in_addr() const
+    {
+        return reinterpret_cast<const struct in_addr&>(_addr);
+    }
+
+    constexpr friend
+    IP4Address operator & (IP4Address l, IP4Address r)
+    {
+        return IP4Address({
+                static_cast<uint8_t>(l._addr[0] & r._addr[0]),
+                static_cast<uint8_t>(l._addr[1] & r._addr[1]),
+                static_cast<uint8_t>(l._addr[2] & r._addr[2]),
+                static_cast<uint8_t>(l._addr[3] & r._addr[3]),
+        });
+    }
+
+    IP4Address& operator &= (IP4Address m)
+    { return *this = *this & m; }
+
+    const uint8_t *bytes() const
+    { return _addr; }
+
+    constexpr friend
+    bool operator < (IP4Address l, IP4Address r)
+    {
+        return _ce_a_lt(l._addr, r._addr);
+    }
+
+    constexpr friend
+    bool operator > (IP4Address l, IP4Address r)
+    {
+        return _ce_a_lt(r._addr, l._addr);
+    }
+
+    constexpr friend
+    bool operator >= (IP4Address l, IP4Address r)
+    {
+        return !_ce_a_lt(l._addr, r._addr);
+    }
+
+    constexpr friend
+    bool operator <= (IP4Address l, IP4Address r)
+    {
+        return !_ce_a_lt(r._addr, l._addr);
+    }
+
+    constexpr friend
+    bool operator == (IP4Address l, IP4Address r)
+    {
+        return !(l < r || r < l);
+    }
+
+    constexpr friend
+    bool operator != (IP4Address l, IP4Address r)
+    {
+        return (l < r || r < l);
+    }
+};
+
+class IP4Mask
+{
+    IP4Address _addr, _mask;
+public:
+    constexpr
+    IP4Mask() : _addr(), _mask()
+    {}
+    constexpr
+    IP4Mask(IP4Address a, IP4Address m) : _addr(a & m), _mask(m)
+    {}
+
+    constexpr
+    IP4Address addr() const
+    { return _addr; }
+    constexpr
+    IP4Address mask() const
+    { return _mask; }
+
+    constexpr
+    bool covers(IP4Address a) const
+    {
+        return (a & _mask) == _addr;
+    }
+};
+
+
+constexpr
+IP4Address IP4_LOCALHOST({127, 0, 0, 1});
+constexpr
+IP4Address IP4_BROADCAST({255, 255, 255, 255});
+
+
+VString<15> convert_for_printf(IP4Address a);
+VString<31> convert_for_printf(IP4Mask m);
+
+bool extract(XString str, IP4Address *iv);
+
+bool extract(XString str, IP4Mask *iv);
+
+#endif // TMWA_MMO_IP_HPP
diff --git a/src/mmo/ip.py b/src/mmo/ip.py
new file mode 100644
index 0000000..e6a8183
--- /dev/null
+++ b/src/mmo/ip.py
@@ -0,0 +1,14 @@
+class IP4Address(object):
+    ''' print an IP4Address
+    '''
+    __slots__ = ('_value')
+    name = 'IP4Address'
+    enabled = True
+
+    def __init__(self, value):
+        self._value = value
+
+    def to_string(self):
+        addr = self._value['_addr']
+        addr = tuple(int(addr[i]) for i in range(4))
+        return '%d.%d.%d.%d' % addr
diff --git a/src/mmo/ip_test.cpp b/src/mmo/ip_test.cpp
new file mode 100644
index 0000000..7ef1047
--- /dev/null
+++ b/src/mmo/ip_test.cpp
@@ -0,0 +1,332 @@
+#include "ip.hpp"
+
+#include <gtest/gtest.h>
+
+#include "../io/cxxstdio.hpp"
+
+#define CB(X) (std::integral_constant<bool, (X)>::value)
+TEST(ip4addr, cmp)
+{
+    constexpr static
+    IP4Address a = IP4_LOCALHOST;
+    constexpr static
+    IP4Address b = IP4_BROADCAST;
+
+    EXPECT_FALSE(CB(a < a));
+    EXPECT_TRUE (CB(a < b));
+    EXPECT_FALSE(CB(b < a));
+    EXPECT_FALSE(CB(b < b));
+
+    EXPECT_FALSE(CB(a > a));
+    EXPECT_FALSE(CB(a > b));
+    EXPECT_TRUE (CB(b > a));
+    EXPECT_FALSE(CB(b > b));
+
+    EXPECT_TRUE (CB(a <= a));
+    EXPECT_TRUE (CB(a <= b));
+    EXPECT_FALSE(CB(b <= a));
+    EXPECT_TRUE (CB(b <= b));
+
+    EXPECT_TRUE (CB(a >= a));
+    EXPECT_FALSE(CB(a >= b));
+    EXPECT_TRUE (CB(b >= a));
+    EXPECT_TRUE (CB(b >= b));
+
+    EXPECT_TRUE (CB(a == a));
+    EXPECT_FALSE(CB(a == b));
+    EXPECT_FALSE(CB(b == a));
+    EXPECT_TRUE (CB(b == b));
+
+    EXPECT_FALSE(CB(a != a));
+    EXPECT_TRUE (CB(a != b));
+    EXPECT_TRUE (CB(b != a));
+    EXPECT_FALSE(CB(b != b));
+}
+
+TEST(ip4addr, str)
+{
+    IP4Address a;
+    EXPECT_EQ("0.0.0.0", STRNPRINTF(17, "%s", a));
+    EXPECT_EQ("127.0.0.1", STRNPRINTF(17, "%s", IP4_LOCALHOST));
+    EXPECT_EQ("255.255.255.255", STRNPRINTF(17, "%s", IP4_BROADCAST));
+}
+
+TEST(ip4addr, extract)
+{
+    IP4Address a;
+    EXPECT_TRUE(extract("0.0.0.0", &a));
+    EXPECT_EQ("0.0.0.0", STRNPRINTF(16, "%s", a));
+    EXPECT_TRUE(extract("127.0.0.1", &a));
+    EXPECT_EQ("127.0.0.1", STRNPRINTF(16, "%s", a));
+    EXPECT_TRUE(extract("255.255.255.255", &a));
+    EXPECT_EQ("255.255.255.255", STRNPRINTF(16, "%s", a));
+    EXPECT_TRUE(extract("1.2.3.4", &a));
+    EXPECT_EQ("1.2.3.4", STRNPRINTF(16, "%s", a));
+
+    EXPECT_FALSE(extract("1.2.3.4.5", &a));
+    EXPECT_FALSE(extract("1.2.3.4.", &a));
+    EXPECT_FALSE(extract("1.2.3.", &a));
+    EXPECT_FALSE(extract("1.2.3", &a));
+    EXPECT_FALSE(extract("1.2.", &a));
+    EXPECT_FALSE(extract("1.2", &a));
+    EXPECT_FALSE(extract("1.", &a));
+    EXPECT_FALSE(extract("1", &a));
+    EXPECT_FALSE(extract("", &a));
+}
+
+
+TEST(ip4mask, body)
+{
+    IP4Mask m;
+    EXPECT_EQ(IP4Address(), m.addr());
+    EXPECT_EQ(IP4Address(), m.mask());
+    m = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST);
+    EXPECT_EQ(IP4_LOCALHOST, m.addr());
+    EXPECT_EQ(IP4_BROADCAST, m.mask());
+}
+
+TEST(ip4mask, str)
+{
+    IP4Mask m;
+    EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(33, "%s", m));
+    m = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST);
+    EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(33, "%s", m));
+}
+
+TEST(ip4mask, extract)
+{
+    IP4Mask m;
+    EXPECT_FALSE(extract("9.8.7.6/33", &m));
+    EXPECT_FALSE(extract("9.8.7.6.5", &m));
+    EXPECT_FALSE(extract("9.8.7.6/", &m));
+    EXPECT_FALSE(extract("9.8.7", &m));
+    EXPECT_FALSE(extract("9.8", &m));
+    EXPECT_FALSE(extract("9", &m));
+
+    EXPECT_TRUE(extract("127.0.0.1", &m));
+    EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("127.0.0.1.", &m));
+    EXPECT_EQ("127.0.0.1/255.255.255.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("127.0.0.", &m));
+    EXPECT_EQ("127.0.0.0/255.255.255.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("127.0.", &m));
+    EXPECT_EQ("127.0.0.0/255.255.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("127.", &m));
+    EXPECT_EQ("127.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m));
+
+    EXPECT_TRUE(extract("1.2.3.4/255.255.255.255", &m));
+    EXPECT_EQ("1.2.3.4/255.255.255.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.2.3.0/255.255.255.0", &m));
+    EXPECT_EQ("1.2.3.0/255.255.255.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.2.0.4/255.255.0.255", &m));
+    EXPECT_EQ("1.2.0.4/255.255.0.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.2.0.0/255.255.0.0", &m));
+    EXPECT_EQ("1.2.0.0/255.255.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.0.3.4/255.0.255.255", &m));
+    EXPECT_EQ("1.0.3.4/255.0.255.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.0.3.0/255.0.255.0", &m));
+    EXPECT_EQ("1.0.3.0/255.0.255.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.0.0.4/255.0.0.255", &m));
+    EXPECT_EQ("1.0.0.4/255.0.0.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("1.0.0.0/255.0.0.0", &m));
+    EXPECT_EQ("1.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.2.3.4/0.255.255.255", &m));
+    EXPECT_EQ("0.2.3.4/0.255.255.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.2.3.0/0.255.255.0", &m));
+    EXPECT_EQ("0.2.3.0/0.255.255.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.2.0.4/0.255.0.255", &m));
+    EXPECT_EQ("0.2.0.4/0.255.0.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.2.0.0/0.255.0.0", &m));
+    EXPECT_EQ("0.2.0.0/0.255.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.3.4/0.0.255.255", &m));
+    EXPECT_EQ("0.0.3.4/0.0.255.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.3.0/0.0.255.0", &m));
+    EXPECT_EQ("0.0.3.0/0.0.255.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.4/0.0.0.255", &m));
+    EXPECT_EQ("0.0.0.4/0.0.0.255", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/0.0.0.0", &m));
+    EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(32, "%s", m));
+
+    // please don't do this
+    EXPECT_TRUE(extract("120.248.200.217/89.57.126.5", &m));
+    EXPECT_EQ("88.56.72.1/89.57.126.5", STRNPRINTF(32, "%s", m));
+
+    EXPECT_TRUE(extract("0.0.0.0/32", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.255", STRNPRINTF(32, "%s", m));
+
+    EXPECT_TRUE(extract("0.0.0.0/31", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.254", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/30", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.252", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/29", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.248", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/28", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.240", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/27", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.224", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/26", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.192", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/25", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.128", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/24", &m));
+    EXPECT_EQ("0.0.0.0/255.255.255.0", STRNPRINTF(32, "%s", m));
+
+    EXPECT_TRUE(extract("0.0.0.0/23", &m));
+    EXPECT_EQ("0.0.0.0/255.255.254.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/22", &m));
+    EXPECT_EQ("0.0.0.0/255.255.252.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/21", &m));
+    EXPECT_EQ("0.0.0.0/255.255.248.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/20", &m));
+    EXPECT_EQ("0.0.0.0/255.255.240.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/19", &m));
+    EXPECT_EQ("0.0.0.0/255.255.224.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/18", &m));
+    EXPECT_EQ("0.0.0.0/255.255.192.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/17", &m));
+    EXPECT_EQ("0.0.0.0/255.255.128.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/16", &m));
+    EXPECT_EQ("0.0.0.0/255.255.0.0", STRNPRINTF(32, "%s", m));
+
+    EXPECT_TRUE(extract("0.0.0.0/15", &m));
+    EXPECT_EQ("0.0.0.0/255.254.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/14", &m));
+    EXPECT_EQ("0.0.0.0/255.252.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/13", &m));
+    EXPECT_EQ("0.0.0.0/255.248.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/12", &m));
+    EXPECT_EQ("0.0.0.0/255.240.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/11", &m));
+    EXPECT_EQ("0.0.0.0/255.224.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/10", &m));
+    EXPECT_EQ("0.0.0.0/255.192.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/9", &m));
+    EXPECT_EQ("0.0.0.0/255.128.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/8", &m));
+    EXPECT_EQ("0.0.0.0/255.0.0.0", STRNPRINTF(32, "%s", m));
+
+    EXPECT_TRUE(extract("0.0.0.0/7", &m));
+    EXPECT_EQ("0.0.0.0/254.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/6", &m));
+    EXPECT_EQ("0.0.0.0/252.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/5", &m));
+    EXPECT_EQ("0.0.0.0/248.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/4", &m));
+    EXPECT_EQ("0.0.0.0/240.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/3", &m));
+    EXPECT_EQ("0.0.0.0/224.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/2", &m));
+    EXPECT_EQ("0.0.0.0/192.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/1", &m));
+    EXPECT_EQ("0.0.0.0/128.0.0.0", STRNPRINTF(32, "%s", m));
+    EXPECT_TRUE(extract("0.0.0.0/0", &m));
+    EXPECT_EQ("0.0.0.0/0.0.0.0", STRNPRINTF(32, "%s", m));
+}
+
+TEST(ip4mask, cover)
+{
+    IP4Address a;
+    IP4Address b = IP4_BROADCAST;
+    IP4Address l = IP4_LOCALHOST;
+    IP4Address h({127, 255, 255, 255});
+    IP4Address p24l({10, 0, 0, 0});
+    IP4Address p24h({10, 255, 255, 255});
+    IP4Address p20l({172, 16, 0, 0});
+    IP4Address p20h({172, 31, 255, 255});
+    IP4Address p16l({192, 168, 0, 0});
+    IP4Address p16h({192, 168, 255, 255});
+    IP4Mask m;
+    EXPECT_TRUE(m.covers(a));
+    EXPECT_TRUE(m.covers(b));
+    EXPECT_TRUE(m.covers(l));
+    EXPECT_TRUE(m.covers(h));
+    EXPECT_TRUE(m.covers(p24l));
+    EXPECT_TRUE(m.covers(p24h));
+    EXPECT_TRUE(m.covers(p20l));
+    EXPECT_TRUE(m.covers(p20h));
+    EXPECT_TRUE(m.covers(p16l));
+    EXPECT_TRUE(m.covers(p16h));
+    m = IP4Mask(l, a);
+    EXPECT_TRUE(m.covers(a));
+    EXPECT_TRUE(m.covers(b));
+    EXPECT_TRUE(m.covers(l));
+    EXPECT_TRUE(m.covers(h));
+    EXPECT_TRUE(m.covers(p24l));
+    EXPECT_TRUE(m.covers(p24h));
+    EXPECT_TRUE(m.covers(p20l));
+    EXPECT_TRUE(m.covers(p20h));
+    EXPECT_TRUE(m.covers(p16l));
+    EXPECT_TRUE(m.covers(p16h));
+    m = IP4Mask(l, b);
+    EXPECT_FALSE(m.covers(a));
+    EXPECT_FALSE(m.covers(b));
+    EXPECT_TRUE(m.covers(l));
+    EXPECT_FALSE(m.covers(h));
+    EXPECT_FALSE(m.covers(p24l));
+    EXPECT_FALSE(m.covers(p24h));
+    EXPECT_FALSE(m.covers(p20l));
+    EXPECT_FALSE(m.covers(p20h));
+    EXPECT_FALSE(m.covers(p16l));
+    EXPECT_FALSE(m.covers(p16h));
+
+    // but the really useful ones are with partial masks
+    m = IP4Mask(IP4Address({10, 0, 0, 0}), IP4Address({255, 0, 0, 0}));
+    EXPECT_FALSE(m.covers(a));
+    EXPECT_FALSE(m.covers(b));
+    EXPECT_FALSE(m.covers(l));
+    EXPECT_FALSE(m.covers(h));
+    EXPECT_TRUE(m.covers(p24l));
+    EXPECT_TRUE(m.covers(p24h));
+    EXPECT_FALSE(m.covers(p20l));
+    EXPECT_FALSE(m.covers(p20h));
+    EXPECT_FALSE(m.covers(p16l));
+    EXPECT_FALSE(m.covers(p16h));
+    EXPECT_FALSE(m.covers(IP4Address({9, 255, 255, 255})));
+    EXPECT_FALSE(m.covers(IP4Address({11, 0, 0, 0})));
+    m = IP4Mask(IP4Address({127, 0, 0, 0}), IP4Address({255, 0, 0, 0}));
+    EXPECT_FALSE(m.covers(a));
+    EXPECT_FALSE(m.covers(b));
+    EXPECT_TRUE(m.covers(l));
+    EXPECT_TRUE(m.covers(h));
+    EXPECT_FALSE(m.covers(p24l));
+    EXPECT_FALSE(m.covers(p24h));
+    EXPECT_FALSE(m.covers(p20l));
+    EXPECT_FALSE(m.covers(p20h));
+    EXPECT_FALSE(m.covers(p16l));
+    EXPECT_FALSE(m.covers(p16h));
+    EXPECT_FALSE(m.covers(IP4Address({126, 255, 255, 255})));
+    EXPECT_FALSE(m.covers(IP4Address({128, 0, 0, 0})));
+    m = IP4Mask(IP4Address({172, 16, 0, 0}), IP4Address({255, 240, 0, 0}));
+    EXPECT_FALSE(m.covers(a));
+    EXPECT_FALSE(m.covers(b));
+    EXPECT_FALSE(m.covers(l));
+    EXPECT_FALSE(m.covers(h));
+    EXPECT_FALSE(m.covers(p24l));
+    EXPECT_FALSE(m.covers(p24h));
+    EXPECT_TRUE(m.covers(p20l));
+    EXPECT_TRUE(m.covers(p20h));
+    EXPECT_FALSE(m.covers(p16l));
+    EXPECT_FALSE(m.covers(p16h));
+    EXPECT_FALSE(m.covers(IP4Address({172, 15, 255, 255})));
+    EXPECT_FALSE(m.covers(IP4Address({172, 32, 0, 0})));
+    m = IP4Mask(IP4Address({192, 168, 0, 0}), IP4Address({255, 255, 0, 0}));
+    EXPECT_FALSE(m.covers(a));
+    EXPECT_FALSE(m.covers(b));
+    EXPECT_FALSE(m.covers(l));
+    EXPECT_FALSE(m.covers(h));
+    EXPECT_FALSE(m.covers(p24l));
+    EXPECT_FALSE(m.covers(p24h));
+    EXPECT_FALSE(m.covers(p20l));
+    EXPECT_FALSE(m.covers(p20h));
+    EXPECT_TRUE(m.covers(p16l));
+    EXPECT_TRUE(m.covers(p16h));
+    EXPECT_FALSE(m.covers(IP4Address({192, 167, 255, 255})));
+    EXPECT_FALSE(m.covers(IP4Address({192, 169, 0, 0})));
+
+    // OTOH this is crazy
+    EXPECT_TRUE(extract("120.248.200.217/89.57.126.5", &m));
+    EXPECT_TRUE(m.covers(IP4Address({120, 248, 200, 217})));
+    EXPECT_TRUE(m.covers(IP4Address({88, 56, 72, 1})));
+    EXPECT_FALSE(m.covers(IP4Address({88, 56, 72, 0})));
+    EXPECT_FALSE(m.covers(IP4Address({88, 56, 72, 255})));
+}
diff --git a/src/mmo/md5more.cpp b/src/mmo/md5more.cpp
new file mode 100644
index 0000000..51ff5c4
--- /dev/null
+++ b/src/mmo/md5more.cpp
@@ -0,0 +1,128 @@
+#include "md5more.hpp"
+
+#include "../compat/rawmem.hpp"
+
+#include "../generic/random.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "../poison.hpp"
+
+#define X block.data
+
+// TODO - refactor MD5 into a stream, and merge the implementations
+// I once implemented an ostream that does it ...
+MD5_state MD5_from_FILE(io::ReadFile& in)
+{
+    uint64_t total_len = 0;
+
+    uint8_t buf[0x40];
+    uint8_t block_len = 0;
+
+    MD5_state state;
+    MD5_init(&state);
+
+    MD5_block block;
+
+    while (true)
+    {
+        size_t rv = in.get(sign_cast<char *>(buf + block_len), 0x40 - block_len);
+        if (!rv)
+            break;
+        total_len += 8 * rv; // in bits
+        block_len += rv;
+        if (block_len != 0x40)
+            continue;
+        for (int i = 0; i < 0x10; i++)
+            X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
+        MD5_do_block(&state, block);
+        block_len = 0;
+    }
+    // no more input, just pad and append the length
+    buf[block_len] = 0x80;
+    really_memset0(buf + block_len + 1, 0x40 - block_len - 1);
+    if (block_len < 0x38)
+    {
+        for (int i = 0; i < 8; i++)
+            buf[0x38 + i] = total_len >> i * 8;
+    }
+    for (int i = 0; i < 0x10; i++)
+        X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
+    MD5_do_block(&state, block);
+    if (0x38 <= block_len)
+    {
+        really_memset0(buf, 0x38);
+        for (int i = 0; i < 8; i++)
+            buf[0x38 + i] = total_len >> i * 8;
+        for (int i = 0; i < 0x10; i++)
+            X[i] = buf[4 * i + 0] | buf[4 * i + 1] << 8 | buf[4 * i + 2] << 16 | buf[4 * i + 3] << 24;
+        MD5_do_block(&state, block);
+    }
+    return state;
+}
+
+
+// Hash a password with a salt.
+// Whoever wrote this FAILS programming
+AccountCrypt MD5_saltcrypt(AccountPass key, SaltString salt)
+{
+    char cbuf[64] {};
+
+    // hash the key then the salt
+    // buf ends up as a 64-char NUL-terminated string
+    md5_string tbuf, tbuf2;
+    MD5_to_str(MD5_from_string(key), tbuf);
+    MD5_to_str(MD5_from_string(salt), tbuf2);
+    const auto it = std::copy(tbuf.begin(), tbuf.end(), std::begin(cbuf));
+    auto it2 = std::copy(tbuf2.begin(), tbuf2.end(), it);
+    assert(it2 == std::end(cbuf));
+
+    md5_string tbuf3;
+    MD5_to_str(MD5_from_string(XString(std::begin(cbuf), it2, nullptr)), tbuf3);
+
+    VString<31> obuf;
+
+    // This truncates the string, but we have to keep it like that for compatibility
+    SNPRINTF(obuf, 32, "!%s$%s", salt, tbuf3);
+    return stringish<AccountCrypt>(obuf);
+}
+
+SaltString make_salt(void)
+{
+    char salt[5];
+    for (int i = 0; i < 5; i++)
+        // 126 would probably actually be okay
+        salt[i] = random_::in(48, 125);
+    return stringish<SaltString>(XString(salt + 0, salt + 5, nullptr));
+}
+
+bool pass_ok(AccountPass password, AccountCrypt crypted)
+{
+    // crypted is like !salt$hash
+    auto begin = crypted.begin() + 1;
+    auto end = std::find(begin, crypted.end(), '$');
+    SaltString salt = stringish<SaltString>(crypted.xislice(begin, end));
+
+    return crypted == MD5_saltcrypt(password, salt);
+}
+
+// [M|h]ashes up an IP address and a secret key
+// to return a hopefully unique masked IP.
+IP4Address MD5_ip(IP4Address ip)
+{
+    static SaltString secret = make_salt();
+
+    // MD5sum a secret + the IP address
+    VString<31> ipbuf;
+    SNPRINTF(ipbuf, 32, "%s %s", ip, secret);
+    md5_binary obuf;
+    MD5_to_bin(MD5_from_string(ipbuf), obuf);
+
+    // Fold the md5sum to 32 bits, pack the bytes to an in_addr
+    return IP4Address({
+            static_cast<uint8_t>(obuf[0] ^ obuf[1] ^ obuf[8] ^ obuf[9]),
+            static_cast<uint8_t>(obuf[2] ^ obuf[3] ^ obuf[10] ^ obuf[11]),
+            static_cast<uint8_t>(obuf[4] ^ obuf[5] ^ obuf[12] ^ obuf[13]),
+            static_cast<uint8_t>(obuf[6] ^ obuf[7] ^ obuf[14] ^ obuf[15]),
+    });
+}
diff --git a/src/mmo/md5more.hpp b/src/mmo/md5more.hpp
new file mode 100644
index 0000000..0c50cca
--- /dev/null
+++ b/src/mmo/md5more.hpp
@@ -0,0 +1,26 @@
+#ifndef TMWA_MMO_MD5MORE_HPP
+#define TMWA_MMO_MD5MORE_HPP
+
+# include "../generic/md5.hpp"
+
+# include "../io/read.hpp"
+
+# include "ip.hpp"
+# include "mmo.hpp"
+
+MD5_state MD5_from_FILE(io::ReadFile& in);
+
+// whoever wrote this fails basic understanding of
+AccountCrypt MD5_saltcrypt(AccountPass key, SaltString salt);
+
+/// return some random characters
+// Currently, returns a 5-char string
+SaltString make_salt(void);
+
+/// check plaintext password against saved saltcrypt
+bool pass_ok(AccountPass password, AccountCrypt crypted);
+
+/// This returns an IP4Address because it is configurable whether it gets called at all
+IP4Address MD5_ip(IP4Address ip);
+
+#endif // TMWA_MMO_MD5MORE_HPP
diff --git a/src/mmo/mmo.cpp b/src/mmo/mmo.cpp
new file mode 100644
index 0000000..e9893ee
--- /dev/null
+++ b/src/mmo/mmo.cpp
@@ -0,0 +1,3 @@
+#include "mmo.hpp"
+
+#include "../poison.hpp"
diff --git a/src/mmo/mmo.hpp b/src/mmo/mmo.hpp
new file mode 100644
index 0000000..6b3cd53
--- /dev/null
+++ b/src/mmo/mmo.hpp
@@ -0,0 +1,377 @@
+/// Global structures and defines
+#ifndef TMWA_MMO_MMO_HPP
+#define TMWA_MMO_MMO_HPP
+
+# include "../sanity.hpp"
+
+# include "../compat/memory.hpp"
+
+# include "../strings/vstring.hpp"
+
+# include "../generic/enum.hpp"
+
+# include "timer.t.hpp"
+
+// affects CharName
+# define NAME_IGNORING_CASE 1
+
+constexpr int FIFOSIZE_SERVERLINK = 256 * 1024;
+
+constexpr int MAX_MAP_PER_SERVER = 512;
+constexpr int MAX_INVENTORY = 100;
+constexpr int MAX_AMOUNT = 30000;
+constexpr int MAX_ZENY = 1000000000;     // 1G zeny
+
+enum class SkillID : uint16_t;
+constexpr SkillID MAX_SKILL = SkillID(474); // not 450
+constexpr SkillID get_enum_min_value(SkillID) { return SkillID(); }
+constexpr SkillID get_enum_max_value(SkillID) { return MAX_SKILL; }
+
+constexpr int GLOBAL_REG_NUM = 96;
+constexpr int ACCOUNT_REG_NUM = 16;
+constexpr int ACCOUNT_REG2_NUM = 16;
+constexpr interval_t DEFAULT_WALK_SPEED = std::chrono::milliseconds(150);
+constexpr interval_t MIN_WALK_SPEED = interval_t::zero();
+constexpr interval_t MAX_WALK_SPEED = std::chrono::seconds(1);
+constexpr int MAX_STORAGE = 300;
+constexpr int MAX_PARTY = 12;
+
+# define MIN_HAIR_STYLE battle_config.min_hair_style
+# define MAX_HAIR_STYLE battle_config.max_hair_style
+# define MIN_HAIR_COLOR battle_config.min_hair_color
+# define MAX_HAIR_COLOR battle_config.max_hair_color
+# define MIN_CLOTH_COLOR battle_config.min_cloth_color
+# define MAX_CLOTH_COLOR battle_config.max_cloth_color
+
+struct AccountName : VString<23> {};
+struct AccountPass : VString<23> {};
+struct AccountCrypt : VString<39> {};
+struct AccountEmail : VString<39> {};
+struct ServerName : VString<19> {};
+struct PartyName : VString<23> {};
+struct VarName : VString<31> {};
+
+# define DEFAULT_EMAIL stringish<AccountEmail>("a@a.com")
+
+// It is decreed: a mapname shall not contain an extension
+class MapName : public strings::_crtp_string<MapName, MapName, strings::ZPair>
+{
+    VString<15> _impl;
+public:
+    MapName() = default;
+    MapName(VString<15> v) : _impl(v.xislice_h(std::find(v.begin(), v.end(), '.'))) {}
+
+    iterator begin() const { return &*_impl.begin(); }
+    iterator end() const { return &*_impl.end(); }
+    const char *c_str() const { return _impl.c_str(); }
+
+    operator RString() const { return _impl; }
+    operator AString() const { return _impl; }
+    operator TString() const { return _impl; }
+    operator SString() const { return _impl; }
+    operator ZString() const { return _impl; }
+    operator XString() const { return _impl; }
+};
+template<>
+inline
+MapName stringish<MapName>(VString<15> iv)
+{
+    return iv;
+}
+inline
+const char *decay_for_printf(const MapName& vs) { return vs.c_str(); }
+
+// It is decreed: a charname is sometimes case sensitive
+struct CharName
+{
+private:
+    VString<23> _impl;
+public:
+    CharName() = default;
+    explicit CharName(VString<23> name)
+    : _impl(name)
+    {}
+
+    VString<23> to__actual() const
+    {
+        return _impl;
+    }
+    VString<23> to__lower() const
+    {
+        return _impl.to_lower();
+    }
+    VString<23> to__upper() const
+    {
+        return _impl.to_upper();
+    }
+    VString<23> to__canonical() const
+    {
+# if NAME_IGNORING_CASE == 0
+        return to__actual();
+# endif
+# if NAME_IGNORING_CASE == 1
+        return to__lower();
+# endif
+    }
+
+    friend bool operator == (const CharName& l, const CharName& r)
+    { return l.to__canonical() == r.to__canonical(); }
+    friend bool operator != (const CharName& l, const CharName& r)
+    { return l.to__canonical() != r.to__canonical(); }
+    friend bool operator < (const CharName& l, const CharName& r)
+    { return l.to__canonical() < r.to__canonical(); }
+    friend bool operator <= (const CharName& l, const CharName& r)
+    { return l.to__canonical() <= r.to__canonical(); }
+    friend bool operator > (const CharName& l, const CharName& r)
+    { return l.to__canonical() > r.to__canonical(); }
+    friend bool operator >= (const CharName& l, const CharName& r)
+    { return l.to__canonical() >= r.to__canonical(); }
+
+    friend
+    VString<23> convert_for_printf(const CharName& vs) { return vs.to__actual(); }
+};
+template<>
+inline
+CharName stringish<CharName>(VString<23> iv)
+{
+    return CharName(iv);
+}
+
+namespace e
+{
+enum class EPOS : uint16_t
+{
+    ZERO    = 0x0000,
+
+    LEGS    = 0x0001,
+    WEAPON  = 0x0002,
+    GLOVES  = 0x0004,
+    CAPE    = 0x0008,
+    MISC1   = 0x0010,
+    SHIELD  = 0x0020,
+    SHOES   = 0x0040,
+    MISC2   = 0x0080,
+    HAT     = 0x0100,
+    TORSO   = 0x0200,
+
+    ARROW   = 0x8000,
+};
+ENUM_BITWISE_OPERATORS(EPOS)
+
+constexpr EPOS get_enum_min_value(EPOS) { return EPOS(0x0000); }
+constexpr EPOS get_enum_max_value(EPOS) { return EPOS(0xffff); }
+}
+using e::EPOS;
+
+struct item
+{
+    int id;
+    short nameid;
+    short amount;
+    EPOS equip;
+};
+
+struct point
+{
+    MapName map_;
+    short x, y;
+};
+
+namespace e
+{
+enum class SkillFlags : uint16_t;
+}
+using e::SkillFlags;
+
+struct skill_value
+{
+    unsigned short lv;
+    SkillFlags flags;
+
+    friend bool operator == (const skill_value& l, const skill_value& r)
+    {
+        return l.lv == r.lv && l.flags == r.flags;
+    }
+    friend bool operator != (const skill_value& l, const skill_value& r)
+    {
+        return !(l == r);
+    }
+};
+
+struct global_reg
+{
+    VarName str;
+    int value;
+};
+
+// Option and Opt1..3 in map.hpp
+namespace e
+{
+enum class Option : uint16_t;
+constexpr Option get_enum_min_value(Option) { return Option(0x0000); }
+constexpr Option get_enum_max_value(Option) { return Option(0xffff); }
+}
+using e::Option;
+
+enum class ATTR
+{
+    STR = 0,
+    AGI = 1,
+    VIT = 2,
+    INT = 3,
+    DEX = 4,
+    LUK = 5,
+
+    COUNT = 6,
+};
+
+constexpr ATTR ATTRs[6] =
+{
+    ATTR::STR,
+    ATTR::AGI,
+    ATTR::VIT,
+    ATTR::INT,
+    ATTR::DEX,
+    ATTR::LUK,
+};
+
+enum class ItemLook : uint16_t
+{
+    NONE = 0,
+    BLADE = 1, // or some other common weapons
+    _2,
+    SETZER_AND_SCYTHE = 3,
+    _6,
+    STAFF = 10,
+    BOW = 11,
+    _13 = 13,
+    _14 = 14,
+    _16 = 16,
+    SINGLE_HANDED_COUNT = 17,
+
+    DUAL_BLADE = 0x11,
+    DUAL_2 = 0x12,
+    DUAL_6 = 0x13,
+    DUAL_12 = 0x14,
+    DUAL_16 = 0x15,
+    DUAL_26 = 0x16,
+};
+
+enum class SEX : uint8_t
+{
+    FEMALE = 0,
+    MALE = 1,
+    // For items. This is also used as error, sometime.
+    NEUTRAL = 2,
+};
+inline
+char sex_to_char(SEX sex)
+{
+    switch (sex)
+    {
+    case SEX::FEMALE: return 'F';
+    case SEX::MALE: return 'M';
+    default: return '\0';
+    }
+}
+inline
+SEX sex_from_char(char c)
+{
+    switch (c)
+    {
+    case 'F': return SEX::FEMALE;
+    case 'M': return SEX::MALE;
+    default: return SEX::NEUTRAL;
+    }
+}
+
+struct CharKey
+{
+    CharName name;
+    int account_id;
+    int char_id;
+    unsigned char char_num;
+};
+
+struct CharData
+{
+    int partner_id;
+
+    int base_exp, job_exp, zeny;
+
+    short species;
+    short status_point, skill_point;
+    int hp, max_hp, sp, max_sp;
+    Option option;
+    short karma, manner;
+    short hair, hair_color, clothes_color;
+    int party_id;
+
+    ItemLook weapon;
+    short shield;
+    short head_top, head_mid, head_bottom;
+
+    unsigned char base_level, job_level;
+    earray<short, ATTR, ATTR::COUNT> attrs;
+    SEX sex;
+
+    unsigned long mapip;
+    unsigned int mapport;
+
+    struct point last_point, save_point;
+    struct item inventory[MAX_INVENTORY];
+    earray<skill_value, SkillID, MAX_SKILL> skill;
+    int global_reg_num;
+    struct global_reg global_reg[GLOBAL_REG_NUM];
+    int account_reg_num;
+    struct global_reg account_reg[ACCOUNT_REG_NUM];
+    int account_reg2_num;
+    struct global_reg account_reg2[ACCOUNT_REG2_NUM];
+};
+
+struct CharPair
+{
+    CharKey key;
+    std::unique_ptr<CharData> data;
+
+    CharPair()
+    : key{}, data(make_unique<CharData>())
+    {}
+};
+
+struct storage
+{
+    int dirty;
+    int account_id;
+    short storage_status;
+    short storage_amount;
+    struct item storage_[MAX_STORAGE];
+};
+
+//struct map_session_data;
+
+struct GM_Account
+{
+    int account_id;
+    uint8_t level;
+};
+
+struct party_member
+{
+    int account_id;
+    CharName name;
+    MapName map;
+    int leader, online, lv;
+    struct map_session_data *sd;
+};
+
+struct party
+{
+    int party_id;
+    PartyName name;
+    int exp;
+    int item;
+    struct party_member member[MAX_PARTY];
+};
+
+#endif // TMWA_MMO_MMO_HPP
diff --git a/src/mmo/socket.cpp b/src/mmo/socket.cpp
new file mode 100644
index 0000000..73e32a4
--- /dev/null
+++ b/src/mmo/socket.cpp
@@ -0,0 +1,474 @@
+#include "socket.hpp"
+
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+//#include <sys/types.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#include "../io/cxxstdio.hpp"
+#include "core.hpp"
+#include "timer.hpp"
+#include "utils.hpp"
+
+#include "../poison.hpp"
+
+static
+io::FD_Set readfds;
+static
+int fd_max;
+
+static
+const uint32_t RFIFO_SIZE = 65536;
+static
+const uint32_t WFIFO_SIZE = 65536;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+static
+std::array<std::unique_ptr<Session>, FD_SETSIZE> session;
+#pragma GCC diagnostic pop
+
+void set_session(io::FD fd, std::unique_ptr<Session> sess)
+{
+    int f = fd.uncast_dammit();
+    assert (0 <= f && f < FD_SETSIZE);
+    session[f] = std::move(sess);
+}
+Session *get_session(io::FD fd)
+{
+    int f = fd.uncast_dammit();
+    if (0 <= f && f < FD_SETSIZE)
+        return session[f].get();
+    return nullptr;
+}
+void reset_session(io::FD fd)
+{
+    int f = fd.uncast_dammit();
+    assert (0 <= f && f < FD_SETSIZE);
+    session[f] = nullptr;
+}
+int get_fd_max() { return fd_max; }
+IteratorPair<ValueIterator<io::FD, IncrFD>> iter_fds()
+{
+    return {io::FD::cast_dammit(0), io::FD::cast_dammit(fd_max)};
+}
+
+/// clean up by discarding handled bytes
+inline
+void RFIFOFLUSH(Session *s)
+{
+    really_memmove(&s->rdata[0], &s->rdata[s->rdata_pos], RFIFOREST(s));
+    s->rdata_size = RFIFOREST(s);
+    s->rdata_pos = 0;
+}
+
+/// how much room there is to read more data
+inline
+size_t RFIFOSPACE(Session *s)
+{
+    return s->max_rdata - s->rdata_size;
+}
+
+
+/// Discard all input
+static
+void null_parse(Session *s);
+/// Default parser for new connections
+static
+void (*default_func_parse)(Session *) = null_parse;
+
+void set_defaultparse(void (*defaultparse)(Session *))
+{
+    default_func_parse = defaultparse;
+}
+
+/// Read from socket to the queue
+static
+void recv_to_fifo(Session *s)
+{
+    if (s->eof)
+        return;
+
+    ssize_t len = s->fd.read(&s->rdata[s->rdata_size],
+                        RFIFOSPACE(s));
+
+    if (len > 0)
+    {
+        s->rdata_size += len;
+        s->connected = 1;
+    }
+    else
+    {
+        s->eof = 1;
+    }
+}
+
+static
+void send_from_fifo(Session *s)
+{
+    if (s->eof)
+        return;
+
+    ssize_t len = s->fd.write(&s->wdata[0], s->wdata_size);
+
+    if (len > 0)
+    {
+        s->wdata_size -= len;
+        if (s->wdata_size)
+        {
+            really_memmove(&s->wdata[0], &s->wdata[len],
+                     s->wdata_size);
+        }
+        s->connected = 1;
+    }
+    else
+    {
+        s->eof = 1;
+    }
+}
+
+static
+void null_parse(Session *s)
+{
+    PRINTF("null_parse : %d\n", s);
+    RFIFOSKIP(s, RFIFOREST(s));
+}
+
+
+static
+void connect_client(Session *ls)
+{
+    struct sockaddr_in client_address;
+    socklen_t len = sizeof(client_address);
+
+    io::FD fd = ls->fd.accept(reinterpret_cast<struct sockaddr *>(&client_address), &len);
+    if (fd == io::FD())
+    {
+        perror("accept");
+        return;
+    }
+    if (fd.uncast_dammit() >= SOFT_LIMIT)
+    {
+        FPRINTF(stderr, "softlimit reached, disconnecting : %d\n", fd.uncast_dammit());
+        fd.shutdown(SHUT_RDWR);
+        fd.close();
+        return;
+    }
+    if (fd_max <= fd.uncast_dammit())
+    {
+        fd_max = fd.uncast_dammit() + 1;
+    }
+
+    const int yes = 1;
+    /// Allow to bind() again after the server restarts.
+    // Since the socket is still in the TIME_WAIT, there's a possibility
+    // that formerly lost packets might be delivered and confuse the server.
+    fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
+    /// Send packets as soon as possible
+    /// even if the kernel thinks there is too little for it to be worth it!
+    /// Testing shows this is indeed a good idea.
+    fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);
+
+    // Linux-ism: Set socket options to optimize for thin streams
+    // See http://lwn.net/Articles/308919/ and
+    // Documentation/networking/tcp-thin.txt .. Kernel 3.2+
+#ifdef TCP_THIN_LINEAR_TIMEOUTS
+    fd.setsockopt(IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof yes);
+#endif
+#ifdef TCP_THIN_DUPACK
+    fd.setsockopt(IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof yes);
+#endif
+
+    readfds.set(fd);
+
+    fd.fcntl(F_SETFL, O_NONBLOCK);
+
+    set_session(fd, make_unique<Session>());
+    Session *s = get_session(fd);
+    s->fd = fd;
+    s->rdata.new_(RFIFO_SIZE);
+    s->wdata.new_(WFIFO_SIZE);
+
+    s->max_rdata = RFIFO_SIZE;
+    s->max_wdata = WFIFO_SIZE;
+    s->func_recv = recv_to_fifo;
+    s->func_send = send_from_fifo;
+    s->func_parse = default_func_parse;
+    s->client_ip = IP4Address(client_address.sin_addr);
+    s->created = TimeT::now();
+    s->connected = 0;
+}
+
+Session *make_listen_port(uint16_t port)
+{
+    struct sockaddr_in server_address;
+    io::FD fd = io::FD::socket(AF_INET, SOCK_STREAM, 0);
+    if (fd == io::FD())
+    {
+        perror("socket");
+        return nullptr;
+    }
+    if (fd_max <= fd.uncast_dammit())
+        fd_max = fd.uncast_dammit() + 1;
+
+    fd.fcntl(F_SETFL, O_NONBLOCK);
+
+    const int yes = 1;
+    /// Allow to bind() again after the server restarts.
+    // Since the socket is still in the TIME_WAIT, there's a possibility
+    // that formerly lost packets might be delivered and confuse the server.
+    fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
+    /// Send packets as soon as possible
+    /// even if the kernel thinks there is too little for it to be worth it!
+    // I'm not convinced this is a good idea; although in minimizes the
+    // latency for an individual write, it increases traffic in general.
+    fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);
+
+    server_address.sin_family = AF_INET;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#if __GNUC__ > 4 || __GNUC_MINOR__ >= 8
+# pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif
+    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
+    server_address.sin_port = htons(port);
+#pragma GCC diagnostic pop
+
+    if (fd.bind(reinterpret_cast<struct sockaddr *>(&server_address),
+              sizeof(server_address)) == -1)
+    {
+        perror("bind");
+        exit(1);
+    }
+    if (fd.listen(5) == -1)
+    {                           /* error */
+        perror("listen");
+        exit(1);
+    }
+
+    readfds.set(fd);
+
+    set_session(fd, make_unique<Session>());
+    Session *s = get_session(fd);
+    s->fd = fd;
+
+    s->func_recv = connect_client;
+    s->created = TimeT::now();
+    s->connected = 1;
+
+    return s;
+}
+
+Session *make_connection(IP4Address ip, uint16_t port)
+{
+    struct sockaddr_in server_address;
+    io::FD fd = io::FD::socket(AF_INET, SOCK_STREAM, 0);
+    if (fd == io::FD())
+    {
+        perror("socket");
+        return nullptr;
+    }
+    if (fd_max <= fd.uncast_dammit())
+        fd_max = fd.uncast_dammit() + 1;
+
+    const int yes = 1;
+    /// Allow to bind() again after the server restarts.
+    // Since the socket is still in the TIME_WAIT, there's a possibility
+    // that formerly lost packets might be delivered and confuse the server.
+    fd.setsockopt(SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
+    /// Send packets as soon as possible
+    /// even if the kernel thinks there is too little for it to be worth it!
+    // I'm not convinced this is a good idea; although in minimizes the
+    // latency for an individual write, it increases traffic in general.
+    fd.setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes);
+
+    server_address.sin_family = AF_INET;
+    server_address.sin_addr = in_addr(ip);
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#if __GNUC__ > 4 || __GNUC_MINOR__ >= 8
+# pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif
+    server_address.sin_port = htons(port);
+#pragma GCC diagnostic pop
+
+    fd.fcntl(F_SETFL, O_NONBLOCK);
+
+    /// Errors not caught - we must not block
+    /// Let the main select() loop detect when we know the state
+    fd.connect(reinterpret_cast<struct sockaddr *>(&server_address),
+             sizeof(struct sockaddr_in));
+
+    readfds.set(fd);
+
+    set_session(fd, make_unique<Session>());
+    Session *s = get_session(fd);
+    s->fd = fd;
+    s->rdata.new_(RFIFO_SIZE);
+    s->wdata.new_(WFIFO_SIZE);
+
+    s->max_rdata = RFIFO_SIZE;
+    s->max_wdata = WFIFO_SIZE;
+    s->func_recv = recv_to_fifo;
+    s->func_send = send_from_fifo;
+    s->func_parse = default_func_parse;
+    s->created = TimeT::now();
+    s->connected = 1;
+
+    return s;
+}
+
+void delete_session(Session *s)
+{
+    if (!s)
+        return;
+
+    io::FD fd = s->fd;
+    // If this was the highest fd, decrease it
+    // We could add a loop to decrement fd_max further for every null session,
+    // but this is cheap and good enough for the typical case
+    if (fd.uncast_dammit() == fd_max - 1)
+        fd_max--;
+    readfds.clr(fd);
+    {
+        s->rdata.delete_();
+        s->wdata.delete_();
+        s->session_data.reset();
+        reset_session(fd);
+    }
+
+    // just close() would try to keep sending buffers
+    fd.shutdown(SHUT_RDWR);
+    fd.close();
+}
+
+void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size)
+{
+    if (s->max_rdata != rfifo_size && s->rdata_size < rfifo_size)
+    {
+        s->rdata.resize(rfifo_size);
+        s->max_rdata = rfifo_size;
+    }
+    if (s->max_wdata != wfifo_size && s->wdata_size < wfifo_size)
+    {
+        s->wdata.resize(wfifo_size);
+        s->max_wdata = wfifo_size;
+    }
+}
+
+void WFIFOSET(Session *s, size_t len)
+{
+    if (s->wdata_size + len + 16384 > s->max_wdata)
+    {
+        realloc_fifo(s, s->max_rdata, s->max_wdata << 1);
+        PRINTF("socket: %d wdata expanded to %zu bytes.\n", s, s->max_wdata);
+    }
+    if (s->wdata_size + len + 2048 < s->max_wdata)
+        s->wdata_size += len;
+    else
+        FPRINTF(stderr, "socket: %d wdata lost !!\n", s), abort();
+}
+
+void do_sendrecv(interval_t next_ms)
+{
+    bool any = false;
+    io::FD_Set rfd = readfds, wfd;
+    for (io::FD i : iter_fds())
+    {
+        Session *s = get_session(i);
+        if (s)
+        {
+            any = true;
+            if (s->wdata_size)
+                wfd.set(i);
+        }
+    }
+    if (!any)
+    {
+        if (!has_timers())
+        {
+            PRINTF("Shutting down - nothing to do\n");
+            runflag = false;
+        }
+        return;
+    }
+    struct timeval timeout;
+    {
+        std::chrono::seconds next_s = std::chrono::duration_cast<std::chrono::seconds>(next_ms);
+        std::chrono::microseconds next_us = next_ms - next_s;
+        timeout.tv_sec = next_s.count();
+        timeout.tv_usec = next_us.count();
+    }
+    if (io::FD_Set::select(fd_max, &rfd, &wfd, NULL, &timeout) <= 0)
+        return;
+    for (io::FD i : iter_fds())
+    {
+        Session *s = get_session(i);
+        if (!s)
+            continue;
+        if (wfd.isset(i))
+        {
+            if (s->func_send)
+                //send_from_fifo(i);
+                s->func_send(s);
+        }
+        if (rfd.isset(i))
+        {
+            if (s->func_recv)
+                //recv_to_fifo(i);
+                //or connect_client(i);
+                s->func_recv(s);
+        }
+    }
+}
+
+void do_parsepacket(void)
+{
+    for (io::FD i : iter_fds())
+    {
+        Session *s = get_session(i);
+        if (!s)
+            continue;
+        if (!s->connected
+            && static_cast<time_t>(TimeT::now()) - static_cast<time_t>(s->created) > CONNECT_TIMEOUT)
+        {
+            PRINTF("Session #%d timed out\n", s);
+            s->eof = 1;
+        }
+        if (!s->rdata_size && !s->eof)
+            continue;
+        if (s->func_parse)
+        {
+            s->func_parse(s);
+            /// some func_parse may call delete_session
+            s = get_session(i);
+            if (s && s->eof)
+            {
+                delete_session(s);
+                s = nullptr;
+            }
+            if (!s)
+                continue;
+        }
+        /// Reclaim buffer space for what was read
+        RFIFOFLUSH(s);
+    }
+}
+
+void RFIFOSKIP(Session *s, size_t len)
+{
+    s->rdata_pos += len;
+
+    if (s->rdata_size < s->rdata_pos)
+    {
+        FPRINTF(stderr, "too many skip\n");
+        abort();
+    }
+}
diff --git a/src/mmo/socket.hpp b/src/mmo/socket.hpp
new file mode 100644
index 0000000..a77f512
--- /dev/null
+++ b/src/mmo/socket.hpp
@@ -0,0 +1,373 @@
+#ifndef TMWA_MMO_SOCKET_HPP
+#define TMWA_MMO_SOCKET_HPP
+
+# include "../sanity.hpp"
+
+# include <netinet/in.h>
+
+# include <cstdio>
+
+# include <array>
+
+# include "../compat/rawmem.hpp"
+
+# include "../strings/astring.hpp"
+# include "../strings/vstring.hpp"
+# include "../strings/xstring.hpp"
+
+# include "../io/fd.hpp"
+
+# include "dumb_ptr.hpp"
+# include "ip.hpp"
+# include "utils.hpp"
+# include "timer.t.hpp"
+
+struct SessionData
+{
+};
+struct SessionDeleter
+{
+    // defined per-server
+    void operator()(SessionData *sd);
+};
+
+// Struct declaration
+
+struct Session
+{
+    /// Checks whether a newly-connected socket actually does anything
+    TimeT created;
+    bool connected;
+
+    /// Flag needed since structure must be freed in a server-dependent manner
+    bool eof;
+
+    /// Since this is a single-threaded application, it can't block
+    /// These are the read/write queues
+    dumb_ptr<uint8_t[]> rdata, wdata;
+    size_t max_rdata, max_wdata;
+    /// How much is actually in the queue
+    size_t rdata_size, wdata_size;
+    /// How much has already been read from the queue
+    /// Note that there is no need for a wdata_pos
+    size_t rdata_pos;
+
+    IP4Address client_ip;
+
+    /// Send or recieve
+    /// Only called when select() indicates the socket is ready
+    /// If, after that, nothing is read, it sets eof
+    // These could probably be hard-coded with a little work
+    void (*func_recv)(Session *);
+    void (*func_send)(Session *);
+    /// This is the important one
+    /// Set to different functions depending on whether the connection
+    /// is a player or a server/ladmin
+    /// Can be set explicitly or via set_defaultparse
+    void (*func_parse)(Session *);
+    /// Server-specific data type
+    std::unique_ptr<SessionData, SessionDeleter> session_data;
+
+    io::FD fd;
+};
+
+inline
+int convert_for_printf(Session *s)
+{
+    return s->fd.uncast_dammit();
+}
+
+// save file descriptors for important stuff
+constexpr int SOFT_LIMIT = FD_SETSIZE - 50;
+
+// socket timeout to establish a full connection in seconds
+constexpr int CONNECT_TIMEOUT = 15;
+
+
+void set_session(io::FD fd, std::unique_ptr<Session> sess);
+Session *get_session(io::FD fd);
+void reset_session(io::FD fd);
+int get_fd_max();
+
+class IncrFD
+{
+public:
+    static
+    io::FD inced(io::FD v)
+    {
+        return io::FD::cast_dammit(v.uncast_dammit() + 1);
+    }
+};
+IteratorPair<ValueIterator<io::FD, IncrFD>> iter_fds();
+
+
+/// open a socket, bind, and listen. Return an fd, or -1 if socket() fails,
+/// but exit if bind() or listen() fails
+Session *make_listen_port(uint16_t port);
+/// Connect to an address, return a connected socket or -1
+// FIXME - this is IPv4 only!
+Session *make_connection(IP4Address ip, uint16_t port);
+/// free() the structure and close() the fd
+void delete_session(Session *);
+/// Make a the internal queues bigger
+void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size);
+/// Update all sockets that can be read/written from the queues
+void do_sendrecv(interval_t next);
+/// Call the parser function for every socket that has read data
+void do_parsepacket(void);
+
+/// Change the default parser for newly connected clients
+// typically called once per server, but individual clients may identify
+// themselves as servers
+void set_defaultparse(void(*defaultparse)(Session *));
+
+template<class T>
+uint8_t *pod_addressof_m(T& structure)
+{
+    static_assert(is_trivially_copyable<T>::value, "Can only byte-copy POD-ish structs");
+    return &reinterpret_cast<uint8_t&>(structure);
+}
+
+template<class T>
+const uint8_t *pod_addressof_c(const T& structure)
+{
+    static_assert(is_trivially_copyable<T>::value, "Can only byte-copy POD-ish structs");
+    return &reinterpret_cast<const uint8_t&>(structure);
+}
+
+
+/// Check how much can be read
+inline
+size_t RFIFOREST(Session *s)
+{
+    return s->rdata_size - s->rdata_pos;
+}
+/// Read from the queue
+inline
+const void *RFIFOP(Session *s, size_t pos)
+{
+    return &s->rdata[s->rdata_pos + pos];
+}
+inline
+uint8_t RFIFOB(Session *s, size_t pos)
+{
+    return *static_cast<const uint8_t *>(RFIFOP(s, pos));
+}
+inline
+uint16_t RFIFOW(Session *s, size_t pos)
+{
+    return *static_cast<const uint16_t *>(RFIFOP(s, pos));
+}
+inline
+uint32_t RFIFOL(Session *s, size_t pos)
+{
+    return *static_cast<const uint32_t *>(RFIFOP(s, pos));
+}
+template<class T>
+void RFIFO_STRUCT(Session *s, size_t pos, T& structure)
+{
+    really_memcpy(pod_addressof_m(structure), static_cast<const uint8_t *>(RFIFOP(s, pos)), sizeof(T));
+}
+inline
+IP4Address RFIFOIP(Session *s, size_t pos)
+{
+    IP4Address o;
+    RFIFO_STRUCT(s, pos, o);
+    return o;
+}
+template<uint8_t len>
+inline
+VString<len-1> RFIFO_STRING(Session *s, size_t pos)
+{
+    const char *const begin = static_cast<const char *>(RFIFOP(s, pos));
+    const char *const end = begin + len-1;
+    const char *const mid = std::find(begin, end, '\0');
+    return XString(begin, mid, nullptr);
+}
+inline
+AString RFIFO_STRING(Session *s, size_t pos, size_t len)
+{
+    const char *const begin = static_cast<const char *>(RFIFOP(s, pos));
+    const char *const end = begin + len;
+    const char *const mid = std::find(begin, end, '\0');
+    return XString(begin, mid, nullptr);
+}
+inline
+void RFIFO_BUF_CLONE(Session *s, uint8_t *buf, size_t len)
+{
+    really_memcpy(buf, static_cast<const uint8_t *>(RFIFOP(s, 0)), len);
+}
+
+/// Done reading
+void RFIFOSKIP(Session *s, size_t len);
+
+/// Read from an arbitrary buffer
+inline
+const void *RBUFP(const uint8_t *p, size_t pos)
+{
+    return p + pos;
+}
+inline
+uint8_t RBUFB(const uint8_t *p, size_t pos)
+{
+    return *static_cast<const uint8_t *>(RBUFP(p, pos));
+}
+inline
+uint16_t RBUFW(const uint8_t *p, size_t pos)
+{
+    return *static_cast<const uint16_t *>(RBUFP(p, pos));
+}
+inline
+uint32_t RBUFL(const uint8_t *p, size_t pos)
+{
+    return *static_cast<const uint32_t *>(RBUFP(p, pos));
+}
+template<class T>
+void RBUF_STRUCT(const uint8_t *p, size_t pos, T& structure)
+{
+    really_memcpy(pod_addressof_m(structure), p + pos, sizeof(T));
+}
+inline
+IP4Address RBUFIP(const uint8_t *p, size_t pos)
+{
+    IP4Address o;
+    RBUF_STRUCT(p, pos, o);
+    return o;
+}
+template<uint8_t len>
+inline
+VString<len-1> RBUF_STRING(const uint8_t *p, size_t pos)
+{
+    const char *const begin = static_cast<const char *>(RBUFP(p, pos));
+    const char *const end = begin + len-1;
+    const char *const mid = std::find(begin, end, '\0');
+    return XString(begin, mid, nullptr);
+}
+inline
+AString RBUF_STRING(const uint8_t *p, size_t pos, size_t len)
+{
+    const char *const begin = static_cast<const char *>(RBUFP(p, pos));
+    const char *const end = begin + len;
+    const char *const mid = std::find(begin, end, '\0');
+    return XString(begin, mid, nullptr);
+}
+
+
+/// Unused - check how much data can be written
+// the existence of this seems scary
+inline
+size_t WFIFOSPACE(Session *s)
+{
+    return s->max_wdata - s->wdata_size;
+}
+/// Write to the queue
+inline
+void *WFIFOP(Session *s, size_t pos)
+{
+    return &s->wdata[s->wdata_size + pos];
+}
+inline
+uint8_t& WFIFOB(Session *s, size_t pos)
+{
+    return *static_cast<uint8_t *>(WFIFOP(s, pos));
+}
+inline
+uint16_t& WFIFOW(Session *s, size_t pos)
+{
+    return *static_cast<uint16_t *>(WFIFOP(s, pos));
+}
+inline
+uint32_t& WFIFOL(Session *s, size_t pos)
+{
+    return *static_cast<uint32_t *>(WFIFOP(s, pos));
+}
+template<class T>
+void WFIFO_STRUCT(Session *s, size_t pos, T& structure)
+{
+    really_memcpy(static_cast<uint8_t *>(WFIFOP(s, pos)), pod_addressof_c(structure), sizeof(T));
+}
+inline
+IP4Address& WFIFOIP(Session *s, size_t pos)
+{
+    static_assert(is_trivially_copyable<IP4Address>::value, "That was the whole point");
+    return *static_cast<IP4Address *>(WFIFOP(s, pos));
+}
+inline
+void WFIFO_STRING(Session *s, size_t pos, XString str, size_t len)
+{
+    char *const begin = static_cast<char *>(WFIFOP(s, pos));
+    char *const end = begin + len;
+    char *const mid = std::copy(str.begin(), str.end(), begin);
+    std::fill(mid, end, '\0');
+}
+inline
+void WFIFO_ZERO(Session *s, size_t pos, size_t len)
+{
+    uint8_t *b = static_cast<uint8_t *>(WFIFOP(s, pos));
+    uint8_t *e = b + len;
+    std::fill(b, e, '\0');
+}
+inline
+void WFIFO_BUF_CLONE(Session *s, const uint8_t *buf, size_t len)
+{
+    really_memcpy(static_cast<uint8_t *>(WFIFOP(s, 0)), buf, len);
+}
+
+/// Finish writing
+void WFIFOSET(Session *s, size_t len);
+
+/// Write to an arbitrary buffer
+inline
+void *WBUFP(uint8_t *p, size_t pos)
+{
+    return p + pos;
+}
+inline
+uint8_t& WBUFB(uint8_t *p, size_t pos)
+{
+    return *static_cast<uint8_t *>(WBUFP(p, pos));
+}
+inline
+uint16_t& WBUFW(uint8_t *p, size_t pos)
+{
+    return *static_cast<uint16_t *>(WBUFP(p, pos));
+}
+inline
+uint32_t& WBUFL(uint8_t *p, size_t pos)
+{
+    return *static_cast<uint32_t *>(WBUFP(p, pos));
+}
+template<class T>
+void WBUF_STRUCT(uint8_t *p, size_t pos, T& structure)
+{
+    really_memcpy(p + pos, pod_addressof_c(structure), sizeof(T));
+}
+inline
+IP4Address& WBUFIP(uint8_t *p, size_t pos)
+{
+    return *static_cast<IP4Address *>(WBUFP(p, pos));
+}
+inline
+void WBUF_STRING(uint8_t *p, size_t pos, XString s, size_t len)
+{
+    char *const begin = static_cast<char *>(WBUFP(p, pos));
+    char *const end = begin + len;
+    char *const mid = std::copy(s.begin(), s.end(), begin);
+    std::fill(mid, end, '\0');
+}
+inline
+void WBUF_ZERO(uint8_t *p, size_t pos, size_t len)
+{
+    uint8_t *b = static_cast<uint8_t *>(WBUFP(p, pos));
+    uint8_t *e = b + len;
+    std::fill(b, e, '\0');
+}
+
+inline
+void RFIFO_WFIFO_CLONE(Session *rs, Session *ws, size_t len)
+{
+    really_memcpy(static_cast<uint8_t *>(WFIFOP(ws, 0)),
+            static_cast<const uint8_t *>(RFIFOP(rs, 0)), len);
+}
+
+#endif // TMWA_MMO_SOCKET_HPP
diff --git a/src/mmo/timer.cpp b/src/mmo/timer.cpp
new file mode 100644
index 0000000..b5a2a5e
--- /dev/null
+++ b/src/mmo/timer.cpp
@@ -0,0 +1,201 @@
+#include "timer.hpp"
+
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <cassert>
+#include <cstring>
+
+#include <queue>
+
+#include "../strings/zstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "utils.hpp"
+
+#include "../poison.hpp"
+
+struct TimerData
+{
+    /// This will be reset on call, to avoid problems.
+    Timer *owner;
+
+    /// When it will be triggered
+    tick_t tick;
+    /// What will be done
+    timer_func func;
+    /// Repeat rate - 0 for oneshot
+    interval_t interval;
+
+    TimerData(Timer *o, tick_t t, timer_func f, interval_t i)
+    : owner(o)
+    , tick(t)
+    , func(std::move(f))
+    , interval(i)
+    {}
+};
+
+struct TimerCompare
+{
+    /// implement "less than"
+    bool operator() (dumb_ptr<TimerData> l, dumb_ptr<TimerData> r)
+    {
+        // C++ provides a max-heap, but we want
+        // the smallest tick to be the head (a min-heap).
+        return l->tick > r->tick;
+    }
+};
+
+static
+std::priority_queue<dumb_ptr<TimerData>, std::vector<dumb_ptr<TimerData>>, TimerCompare> timer_heap;
+
+
+tick_t gettick_cache;
+
+tick_t milli_clock::now(void) noexcept
+{
+    struct timeval tval;
+    // BUG: This will cause strange behavior if the system clock is changed!
+    // it should be reimplemented in terms of clock_gettime(CLOCK_MONOTONIC, )
+    gettimeofday(&tval, NULL);
+    return gettick_cache = tick_t(std::chrono::seconds(tval.tv_sec)
+            + std::chrono::duration_cast<std::chrono::milliseconds>(
+                std::chrono::microseconds(tval.tv_usec)));
+}
+
+static
+void do_nothing(TimerData *, tick_t)
+{
+}
+
+void Timer::cancel()
+{
+    if (!td)
+        return;
+
+    assert (this == td->owner);
+    td->owner = nullptr;
+    td->func = do_nothing;
+    td->interval = interval_t::zero();
+    td = nullptr;
+}
+
+void Timer::detach()
+{
+    assert (this == td->owner);
+    td->owner = nullptr;
+    td = nullptr;
+}
+
+static
+void push_timer_heap(dumb_ptr<TimerData> td)
+{
+    timer_heap.push(td);
+}
+
+static
+dumb_ptr<TimerData> top_timer_heap(void)
+{
+    if (timer_heap.empty())
+        return dumb_ptr<TimerData>();
+    return timer_heap.top();
+}
+
+static
+void pop_timer_heap(void)
+{
+    timer_heap.pop();
+}
+
+Timer::Timer(tick_t tick, timer_func func, interval_t interval)
+: td(dumb_ptr<TimerData>::make(this, tick, std::move(func), interval))
+{
+    assert (interval >= interval_t::zero());
+
+    push_timer_heap(td);
+}
+
+Timer::Timer(Timer&& t)
+: td(t.td)
+{
+    t.td = nullptr;
+    if (td)
+    {
+        assert (td->owner == &t);
+        td->owner = this;
+    }
+}
+
+Timer& Timer::operator = (Timer&& t)
+{
+    std::swap(td, t.td);
+    if (td)
+    {
+        assert (td->owner == &t);
+        td->owner = this;
+    }
+    if (t.td)
+    {
+        assert (t.td->owner == this);
+        t.td->owner = &t;
+    }
+    return *this;
+}
+
+interval_t do_timer(tick_t tick)
+{
+    /// Number of milliseconds until it calls this again
+    // this says to wait 1 sec if all timers get popped
+    interval_t nextmin = std::chrono::seconds(1);
+
+    while (dumb_ptr<TimerData> td = top_timer_heap())
+    {
+        // while the heap is not empty and
+        if (td->tick > tick)
+        {
+            /// Return the time until the next timer needs to goes off
+            nextmin = td->tick - tick;
+            break;
+        }
+        pop_timer_heap();
+
+        // Prevent destroying the object we're in.
+        // Note: this would be surprising in an interval timer,
+        // but all interval timers do an immediate explicit detach().
+        if (td->owner)
+            td->owner->detach();
+        // If we are too far past the requested tick, call with
+        // the current tick instead to fix reregistration problems
+        if (td->tick + std::chrono::seconds(1) < tick)
+            td->func(td.operator->(), tick);
+        else
+            td->func(td.operator->(), td->tick);
+
+        if (td->interval == interval_t::zero())
+        {
+            td.delete_();
+            continue;
+        }
+        if (td->tick + std::chrono::seconds(1) < tick)
+            td->tick = tick + td->interval;
+        else
+            td->tick += td->interval;
+        push_timer_heap(td);
+    }
+
+    return std::max(nextmin, std::chrono::milliseconds(10));
+}
+
+tick_t file_modified(ZString name)
+{
+    struct stat buf;
+    if (stat(name.c_str(), &buf))
+        return tick_t();
+    return tick_t(std::chrono::seconds(buf.st_mtime));
+}
+
+bool has_timers()
+{
+    return !timer_heap.empty();
+}
diff --git a/src/mmo/timer.hpp b/src/mmo/timer.hpp
new file mode 100644
index 0000000..01b8623
--- /dev/null
+++ b/src/mmo/timer.hpp
@@ -0,0 +1,30 @@
+#ifndef TMWA_MMO_TIMER_HPP
+#define TMWA_MMO_TIMER_HPP
+
+# include "timer.t.hpp"
+
+# include "../sanity.hpp"
+
+# include "../strings/fwd.hpp"
+
+// updated automatically when using milli_clock::now()
+// which is done only by core.cpp
+extern tick_t gettick_cache;
+
+inline
+tick_t gettick(void)
+{
+    return gettick_cache;
+}
+
+/// Do all timers scheduled before tick, and return the number of
+/// milliseconds until the next timer happens
+interval_t do_timer(tick_t tick);
+
+/// Stat a file, and return its modification time, truncated to seconds.
+tick_t file_modified(ZString name);
+
+/// Check if there are any events at all scheduled.
+bool has_timers();
+
+#endif // TMWA_MMO_TIMER_HPP
diff --git a/src/mmo/timer.t.hpp b/src/mmo/timer.t.hpp
new file mode 100644
index 0000000..dcc88f8
--- /dev/null
+++ b/src/mmo/timer.t.hpp
@@ -0,0 +1,68 @@
+#ifndef TMWA_MMO_TIMER_T_HPP
+#define TMWA_MMO_TIMER_T_HPP
+
+# include <chrono>
+# include <functional>
+
+# include "dumb_ptr.hpp"
+
+struct TimerData;
+
+/// An implementation of the C++ "clock" concept, exposing
+/// durations in milliseconds.
+class milli_clock
+{
+public:
+    typedef std::chrono::milliseconds duration;
+    typedef duration::rep rep;
+    typedef duration::period period;
+    typedef std::chrono::time_point<milli_clock, duration> time_point;
+    static const bool is_steady = true; // assumed - not necessarily true
+
+    static time_point now() noexcept;
+};
+
+/// A point in time.
+typedef milli_clock::time_point tick_t;
+/// The difference between two points in time.
+typedef milli_clock::duration interval_t;
+/// (to get additional arguments, use std::bind or a lambda).
+typedef std::function<void (TimerData *, tick_t)> timer_func;
+
+class Timer
+{
+    friend struct TimerData;
+    dumb_ptr<TimerData> td;
+
+    Timer(const Timer&) = delete;
+    Timer& operator = (const Timer&) = delete;
+public:
+    /// Don't own anything yet.
+    Timer() = default;
+    /// Schedule a timer for the given tick.
+    /// If you do not wish to keep track of it, call disconnect().
+    /// Otherwise, you may cancel() or replace (operator =) it later.
+    ///
+    /// If the interval argument is given, the timer will reschedule
+    /// itself again forever. Otherwise, it will disconnect() itself
+    /// just BEFORE it is called.
+    Timer(tick_t tick, timer_func func, interval_t interval=interval_t::zero());
+
+    Timer(Timer&& t);
+    Timer& operator = (Timer&& t);
+    ~Timer() { cancel(); }
+
+    /// Cancel the delivery of this timer's function, and make it falsy.
+    /// Implementation note: this doesn't actually remove it, just sets
+    /// the functor to do_nothing, and waits for the tick before removing.
+    void cancel();
+    /// Make it falsy without cancelling the timer,
+    void detach();
+
+    /// Check if there is a timer connected.
+    explicit operator bool() { return bool(td); }
+    /// Check if there is no connected timer.
+    bool operator !() { return !td; }
+};
+
+#endif // TMWA_MMO_TIMER_T_HPP
diff --git a/src/mmo/utils.cpp b/src/mmo/utils.cpp
new file mode 100644
index 0000000..0dbf145
--- /dev/null
+++ b/src/mmo/utils.cpp
@@ -0,0 +1,101 @@
+#include "utils.hpp"
+
+#include <netinet/in.h>
+#include <sys/time.h>
+
+#include <algorithm>
+
+#include "../strings/astring.hpp"
+#include "../strings/zstring.hpp"
+#include "../strings/xstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/write.hpp"
+
+#include "extract.hpp"
+
+#include "../poison.hpp"
+
+//---------------------------------------------------
+// E-mail check: return 0 (not correct) or 1 (valid).
+//---------------------------------------------------
+bool e_mail_check(XString email)
+{
+    // athena limits
+    if (email.size() < 3 || email.size() > 39)
+        return 0;
+
+    // part of RFC limits (official reference of e-mail description)
+    XString::iterator at = std::find(email.begin(), email.end(), '@');
+    if (at == email.end())
+        return 0;
+    XString username = email.xislice_h(at);
+    XString hostname = email.xislice_t(at + 1);
+    if (!username || !hostname)
+        return 0;
+    if (hostname.contains('@'))
+        return 0;
+    if (hostname.front() == '.' || hostname.back() == '.')
+        return 0;
+    if (hostname.contains_seq(".."))
+        return 0;
+    if (email.contains_any(" ;"))
+        return 0;
+    return email.is_print();
+}
+
+//-------------------------------------------------
+// Return numerical value of a switch configuration
+// on/off, english, français, deutsch, español
+//-------------------------------------------------
+int config_switch(ZString str)
+{
+    if (str == "true" || str == "on" || str == "yes"
+        || str == "oui" || str == "ja"
+        || str == "si")
+        return 1;
+    if (str == "false" || str == "off" || str == "no"
+        || str == "non" || str == "nein")
+        return 0;
+
+    int rv;
+    if (extract(str, &rv))
+        return rv;
+    FPRINTF(stderr, "Fatal: bad option value %s", str);
+    abort();
+}
+
+static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer");
+static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer");
+
+void stamp_time(timestamp_seconds_buffer& out, const TimeT *t)
+{
+    struct tm when = t ? *t : TimeT::now();
+    char buf[20];
+    strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
+    out = stringish<timestamp_seconds_buffer>(const_(buf));
+}
+void stamp_time(timestamp_milliseconds_buffer& out)
+{
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    struct tm when = TimeT(tv.tv_sec);
+    char buf[24];
+    strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
+    sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000));
+    out = stringish<timestamp_milliseconds_buffer>(const_(buf));
+}
+
+void log_with_timestamp(io::WriteFile& out, XString line)
+{
+    if (!line)
+    {
+        out.put('\n');
+        return;
+    }
+    timestamp_milliseconds_buffer tmpstr;
+    stamp_time(tmpstr);
+    out.really_put(tmpstr.data(), tmpstr.size());
+    out.really_put(": ", 2);
+    out.put_line(line);
+}
diff --git a/src/mmo/utils.hpp b/src/mmo/utils.hpp
new file mode 100644
index 0000000..5e9de26
--- /dev/null
+++ b/src/mmo/utils.hpp
@@ -0,0 +1,116 @@
+#ifndef TMWA_MMO_UTILS_HPP
+#define TMWA_MMO_UTILS_HPP
+
+# include "../sanity.hpp"
+
+# include <cstdio>
+
+# include <type_traits>
+
+# include "../strings/fwd.hpp"
+# include "../strings/vstring.hpp"
+
+# include "../generic/const_array.hpp"
+# include "../generic/operators.hpp"
+
+# include "../io/fwd.hpp"
+
+template<class T>
+struct is_trivially_copyable
+: std::integral_constant<bool,
+    // come back when GCC actually implements the public traits properly
+    __has_trivial_copy(T)
+    && __has_trivial_assign(T)
+    && __has_trivial_destructor(T)>
+{};
+
+bool e_mail_check(XString email);
+int config_switch (ZString str);
+
+template<class T>
+void really_memzero_this(T *v)
+{
+    static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types");
+    static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)");
+    memset(v, '\0', sizeof(*v));
+}
+template<class T, size_t n>
+void really_memzero_this(T (&)[n]) = delete;
+
+// Exists in place of time_t, to give it a predictable printf-format.
+// (on x86 and amd64, time_t == long, but not on x32)
+static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t");
+struct TimeT : Comparable
+{
+    long long value;
+
+    // conversion
+    TimeT(time_t t=0) : value(t) {}
+    TimeT(struct tm t) : value(timegm(&t)) {}
+    operator time_t() const { return value; }
+    operator struct tm() const { time_t v = value; return *gmtime(&v); }
+
+    explicit operator bool() const { return value; }
+    bool operator !() const { return !value; }
+
+    // prevent surprises
+    template<class T>
+    TimeT(T) = delete;
+    template<class T>
+    operator T() const = delete;
+
+    static
+    TimeT now()
+    {
+        // poisoned, but this is still in header-land
+        return time(NULL);
+    }
+
+    bool error() const
+    {
+        return value == -1;
+    }
+    bool okay() const
+    {
+        return !error();
+    }
+};
+
+inline
+long long convert_for_printf(TimeT t)
+{
+    return t.value;
+}
+
+inline
+long long& convert_for_scanf(TimeT& t)
+{
+    return t.value;
+}
+
+struct timestamp_seconds_buffer : VString<19> {};
+struct timestamp_milliseconds_buffer : VString<23> {};
+void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr);
+void stamp_time(timestamp_milliseconds_buffer&);
+
+void log_with_timestamp(io::WriteFile& out, XString line);
+
+// TODO VString?
+# define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS"
+static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer),
+        "timestamp size");
+# define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY
+//  str:            prefix: YYYY-MM-DD HH:MM:SS
+//  sizeof:        01234567890123456789012345678
+//  str + sizeof:                               ^
+//  -1:                     ^
+// there's probably a better way to do this now
+# define REPLACE_TIMESTAMP(str, t)                          \
+    stamp_time(                                             \
+            reinterpret_cast<timestamp_seconds_buffer *>(   \
+                str + sizeof(str)                           \
+            )[-1],                                          \
+            &t                                              \
+    )
+
+#endif // TMWA_MMO_UTILS_HPP
diff --git a/src/mmo/version.cpp b/src/mmo/version.cpp
new file mode 100644
index 0000000..dd18fe1
--- /dev/null
+++ b/src/mmo/version.cpp
@@ -0,0 +1,55 @@
+#include "version.hpp"
+
+#include "../conf/version.hpp"
+
+#include "../strings/xstring.hpp"
+
+#include "extract.hpp"
+
+#include "../poison.hpp"
+
+Version CURRENT_VERSION =
+{
+    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
+    VERSION_DEVEL,
+
+    0, 0,
+    VENDOR_VERSION,
+};
+Version CURRENT_LOGIN_SERVER_VERSION =
+{
+    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
+    VERSION_DEVEL,
+
+    0, TMWA_SERVER_LOGIN,
+    VENDOR_VERSION,
+};
+Version CURRENT_CHAR_SERVER_VERSION =
+{
+    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
+    VERSION_DEVEL,
+
+    0, TMWA_SERVER_CHAR | TMWA_SERVER_INTER,
+    VENDOR_VERSION,
+};
+Version CURRENT_MAP_SERVER_VERSION =
+{
+    VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH,
+    VERSION_DEVEL,
+
+    0, TMWA_SERVER_MAP,
+    VENDOR_VERSION,
+};
+
+#define S2(a) #a
+#define S(a) S2(a)
+
+const char CURRENT_VERSION_STRING[] = "TMWA "
+        S(VERSION_MAJOR) "." S(VERSION_MINOR) "." S(VERSION_PATCH)
+        " dev" S(VERSION_DEVEL) " (" VENDOR " " S(VENDOR_VERSION) ")";
+
+bool extract(XString str, Version *vers)
+{
+    *vers = {};
+    return extract(str, record<'.'>(&vers->major, &vers->minor, &vers->patch));
+}
diff --git a/src/mmo/version.hpp b/src/mmo/version.hpp
new file mode 100644
index 0000000..420cbf9
--- /dev/null
+++ b/src/mmo/version.hpp
@@ -0,0 +1,69 @@
+#ifndef TMWA_MMO_VERSION_HPP
+#define TMWA_MMO_VERSION_HPP
+
+# include <cstdint>
+
+# include "../strings/fwd.hpp"
+
+// TODO make these bitwise enums
+# define TMWA_FLAG_REGISTRATION 0x01
+
+# define TMWA_SERVER_LOGIN      0x01
+# define TMWA_SERVER_CHAR       0x02
+# define TMWA_SERVER_INTER      0x04
+# define TMWA_SERVER_MAP        0x08
+
+struct Version
+{
+    uint8_t major;
+    uint8_t minor; // flavor1
+    uint8_t patch; // flavor2
+    uint8_t devel; // flavor3
+
+    uint8_t flags;
+    uint8_t which;
+    uint16_t vend;
+    // can't add vendor name yet
+};
+static_assert(sizeof(Version) == 8, "this is sent over the network, can't change");
+
+extern Version CURRENT_VERSION;
+
+extern Version CURRENT_LOGIN_SERVER_VERSION;
+extern Version CURRENT_CHAR_SERVER_VERSION;
+extern Version CURRENT_MAP_SERVER_VERSION;
+
+extern const char CURRENT_VERSION_STRING[];
+
+bool extract(XString str, Version *vers);
+
+constexpr
+bool operator < (Version l, Version r)
+{
+    return (l.major < r.major
+            || (l.major == r.major
+                && (l.minor < r.minor
+                    || (l.minor == r.minor
+                        && (l.patch < r.patch
+                            || (l.patch == r.patch
+                                && (l.devel < r.devel
+                                    || (l.devel == r.devel
+                                        && l.vend < r.vend))))))));
+}
+constexpr
+bool operator > (Version l, Version r)
+{
+    return r < l;
+}
+constexpr
+bool operator <= (Version l, Version r)
+{
+    return !(r < l);
+}
+constexpr
+bool operator >= (Version l, Version r)
+{
+    return !(l < r);
+}
+
+#endif // TMWA_MMO_VERSION_HPP
diff --git a/src/monitor/GNUmakefile b/src/monitor/GNUmakefile
deleted file mode 100644
index 42efa8b..0000000
--- a/src/monitor/GNUmakefile
+++ /dev/null
@@ -1,7 +0,0 @@
-.SUFFIXES:
-bin/tmwa-monitor:
-	${MAKE} -C ../.. bin/tmwa-monitor
-clean:
-	rm -r ../../obj/monitor/
-%::
-	${MAKE} -C ../.. obj/monitor/$@
diff --git a/src/monitor/main.cpp b/src/monitor/main.cpp
index e59be71..d2d22c4 100644
--- a/src/monitor/main.cpp
+++ b/src/monitor/main.cpp
@@ -23,8 +23,8 @@
 #include "../io/fd.hpp"
 #include "../io/read.hpp"
 
-#include "../common/config_parse.hpp"
-#include "../common/utils.hpp"
+#include "../mmo/config_parse.hpp"
+#include "../mmo/utils.hpp"
 
 #include "../poison.hpp"
 
diff --git a/src/sanity.hpp b/src/sanity.hpp
index d4a8738..b048c36 100644
--- a/src/sanity.hpp
+++ b/src/sanity.hpp
@@ -10,11 +10,6 @@
 #  error "Your compiler is absolutely ancient. You have no chance ..."
 # endif // __GNUC__ < 4
 
-/// Convert type assumptions to use the standard types here
-# include <cstdint>
-/// size_t, NULL
-# include <cstddef>
-
 # if __GNUC__ == 4
 // clang identifies as GCC 4.2, but is mostly okay.
 // Until a bug-free release of it happens, though, I won't recommend it.
diff --git a/src/strings/astring.cpp b/src/strings/astring.cpp
index f7cfa2e..f1e9030 100644
--- a/src/strings/astring.cpp
+++ b/src/strings/astring.cpp
@@ -25,6 +25,8 @@
 #include "xstring.hpp"
 #include "vstring.hpp"
 
+//#include "../poison.hpp"
+
 namespace strings
 {
     static_assert(sizeof(AString) == 256, "AString");
diff --git a/src/strings/base.hpp b/src/strings/base.hpp
index c081f70..cd8d46f 100644
--- a/src/strings/base.hpp
+++ b/src/strings/base.hpp
@@ -22,6 +22,8 @@
 # include "fwd.hpp"
 # include "pair.hpp"
 
+# include <cstddef>
+
 # include <iterator>
 
 // It is a common mistake to assume that one string class for everything.
diff --git a/src/strings/fwd.hpp b/src/strings/fwd.hpp
index f2b4037..4c58e03 100644
--- a/src/strings/fwd.hpp
+++ b/src/strings/fwd.hpp
@@ -21,6 +21,8 @@
 
 # include "../sanity.hpp"
 
+# include <cstdint>
+
 // It is a common mistake to assume that one string class for everything.
 // Because C++ and TMWA have a C legacy, there are a few more here
 // than would probably be necessary in an ideal language.
diff --git a/src/strings/mstring.cpp b/src/strings/mstring.cpp
index 0d496a5..2a1ca62 100644
--- a/src/strings/mstring.cpp
+++ b/src/strings/mstring.cpp
@@ -20,6 +20,8 @@
 
 #include "xstring.hpp"
 
+#include "../poison.hpp"
+
 namespace strings
 {
     MString::iterator MString::begin()
diff --git a/src/strings/rstring.cpp b/src/strings/rstring.cpp
index eb9d88a..c0e231e 100644
--- a/src/strings/rstring.cpp
+++ b/src/strings/rstring.cpp
@@ -25,6 +25,8 @@
 #include "xstring.hpp"
 #include "vstring.hpp"
 
+//#include "../poison.hpp"
+
 namespace strings
 {
     static_assert(sizeof(RString) == sizeof(const char *), "RString");
diff --git a/src/strings/sstring.cpp b/src/strings/sstring.cpp
index fee98f9..76f0994 100644
--- a/src/strings/sstring.cpp
+++ b/src/strings/sstring.cpp
@@ -22,6 +22,8 @@
 #include "zstring.hpp"
 #include "xstring.hpp"
 
+#include "../poison.hpp"
+
 namespace strings
 {
     SString::SString()
diff --git a/src/strings/tstring.cpp b/src/strings/tstring.cpp
index 8be7112..5f463ca 100644
--- a/src/strings/tstring.cpp
+++ b/src/strings/tstring.cpp
@@ -22,6 +22,8 @@
 #include "zstring.hpp"
 #include "xstring.hpp"
 
+#include "../poison.hpp"
+
 namespace strings
 {
     TString::TString()
diff --git a/src/strings/vstring.cpp b/src/strings/vstring.cpp
index 6a0416a..0ef8f3d 100644
--- a/src/strings/vstring.cpp
+++ b/src/strings/vstring.cpp
@@ -18,6 +18,8 @@
 //    You should have received a copy of the GNU General Public License
 //    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#include "../poison.hpp"
+
 namespace strings
 {
 } // namespace strings
diff --git a/src/strings/vstring.hpp b/src/strings/vstring.hpp
index 183e782..c467b64 100644
--- a/src/strings/vstring.hpp
+++ b/src/strings/vstring.hpp
@@ -65,6 +65,14 @@ namespace strings
     int do_vprint(VString<len>& out, const char *fmt, va_list ap);
 } // namespace strings
 
+template<class T>
+T stringish(VString<sizeof(T) - 1> iv)
+{
+    T rv;
+    static_cast<VString<sizeof(T) - 1>&>(rv) = iv;
+    return rv;
+}
+
 # include "vstring.tcc"
 
 #endif // TMWA_STRINGS_VSTRING_HPP
diff --git a/src/strings/vstring.tcc b/src/strings/vstring.tcc
index 69729d4..1aa163d 100644
--- a/src/strings/vstring.tcc
+++ b/src/strings/vstring.tcc
@@ -19,7 +19,7 @@
 
 #include <cassert>
 
-#include "../common/utils2.hpp"
+#include "../compat/cast.hpp"
 
 #include "rstring.hpp"
 #include "astring.hpp"
diff --git a/src/strings/xstring.cpp b/src/strings/xstring.cpp
index 8a604c7..0808104 100644
--- a/src/strings/xstring.cpp
+++ b/src/strings/xstring.cpp
@@ -18,6 +18,8 @@
 //    You should have received a copy of the GNU General Public License
 //    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#include "../poison.hpp"
+
 namespace strings
 {
     XString::XString()
diff --git a/src/strings/zstring.cpp b/src/strings/zstring.cpp
index bfc0c96..e2a763f 100644
--- a/src/strings/zstring.cpp
+++ b/src/strings/zstring.cpp
@@ -20,6 +20,8 @@
 
 #include "xstring.hpp"
 
+#include "../poison.hpp"
+
 namespace strings
 {
     ZString::ZString()
-- 
cgit v1.2.3-70-g09d2