--- /dev/null
+#include "error.hpp"
+
+#include <alut.h>
+#include <cerrno>
+#include <cstring>
+#include <SDL.h>
+#include <SDL_net.h>
+#include <SDL_ttf.h>
+#include <GL/glew.h>
+
+using namespace std;
+
+
+namespace {
+
+string alut_error_append(ALenum num, string msg) {
+       const char *error = alutGetErrorString(num);
+       if (error && *error != '\0') {
+               msg += ": ";
+               msg += error;
+       }
+       return msg;
+}
+
+string gl_error_append(string msg) {
+       const GLubyte *error = gluErrorString(glGetError());
+       if (error && *error != '\0') {
+               const GLubyte *errEnd = error;
+               while (*errEnd != '\0') {
+                       ++errEnd;
+               }
+               msg += ": ";
+               msg.append(error, errEnd);
+       }
+       return msg;
+}
+
+string gl_error_get() {
+       string msg;
+       const GLubyte *error = gluErrorString(glGetError());
+       if (error && *error != '\0') {
+               const GLubyte *errEnd = error;
+               while (*errEnd != '\0') {
+                       ++errEnd;
+               }
+               msg.assign(error, errEnd);
+       }
+       return msg;
+}
+
+string net_error_append(string msg) {
+       const char *error = SDLNet_GetError();
+       if (*error != '\0') {
+               msg += ": ";
+               msg += error;
+       }
+       return msg;
+}
+
+string sdl_error_append(string msg) {
+       const char *error = SDL_GetError();
+       if (error && *error != '\0') {
+               msg += ": ";
+               msg += error;
+               SDL_ClearError();
+       }
+       return msg;
+}
+
+string ttf_error_append(string msg) {
+       const char *error = TTF_GetError();
+       if (error && *error != '\0') {
+               msg += ": ";
+               msg += error;
+       }
+       return msg;
+}
+
+}
+
+
+namespace blank {
+
+AlutError::AlutError(ALenum num)
+: runtime_error(alutGetErrorString(num)) {
+
+}
+
+AlutError::AlutError(ALenum num, const string &msg)
+: runtime_error(alut_error_append(num, msg)) {
+
+}
+
+
+GLError::GLError()
+: runtime_error(gl_error_get()) {
+
+}
+
+GLError::GLError(const string &msg)
+: runtime_error(gl_error_append(msg)) {
+
+}
+
+
+NetError::NetError()
+: runtime_error(SDLNet_GetError()) {
+
+}
+
+NetError::NetError(const string &msg)
+: runtime_error(net_error_append(msg)) {
+
+}
+
+
+SDLError::SDLError()
+: runtime_error(SDL_GetError()) {
+
+}
+
+SDLError::SDLError(const string &msg)
+: runtime_error(sdl_error_append(msg)) {
+
+}
+
+
+SysError::SysError()
+: SysError(errno) {
+
+}
+
+SysError::SysError(const string &msg)
+: SysError(errno, msg) {
+
+}
+
+SysError::SysError(int err_num)
+: runtime_error(strerror(err_num)) {
+
+}
+
+SysError::SysError(int err_num, const string &msg)
+: runtime_error(msg + ": " + strerror(err_num)) {
+
+}
+
+
+TTFError::TTFError()
+: runtime_error(TTF_GetError()) {
+
+}
+
+TTFError::TTFError(const string &msg)
+: runtime_error(ttf_error_append(msg)) {
+
+}
+
+}
 
--- /dev/null
+#ifndef BLANK_APP_ERROR_HPP_
+#define BLANK_APP_ERROR_HPP_
+
+#include <al.h>
+#include <stdexcept>
+#include <string>
+
+
+namespace blank {
+
+class AlutError
+: public std::runtime_error {
+
+public:
+       explicit AlutError(ALenum);
+       AlutError(ALenum, const std::string &);
+
+};
+
+
+class GLError
+: public std::runtime_error {
+
+public:
+       GLError();
+       explicit GLError(const std::string &);
+
+};
+
+
+class NetError
+: public std::runtime_error {
+
+public:
+       NetError();
+       explicit NetError(const std::string &);
+
+};
+
+
+class SDLError
+: public std::runtime_error {
+
+public:
+       SDLError();
+       explicit SDLError(const std::string &);
+
+};
+
+
+class SysError
+: public std::runtime_error {
+
+public:
+       SysError();
+       explicit SysError(const std::string &);
+       explicit SysError(int err_num);
+       SysError(int err_num, const std::string &);
+
+};
+
+
+class TTFError
+: public std::runtime_error {
+
+public:
+       TTFError();
+       explicit TTFError(const std::string &);
+
+};
+
+}
+
+#endif
 
 #include <SDL_image.h>
 #include <SDL_net.h>
 #include <SDL_ttf.h>
+#include <string>
 #include <GL/glew.h>
 
 
-namespace {
-
-std::string sdl_error_append(std::string msg) {
-       const char *error = SDL_GetError();
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-               SDL_ClearError();
-       }
-       return msg;
-}
-
-std::string net_error_append(std::string msg) {
-       const char *error = SDLNet_GetError();
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-       }
-       return msg;
-}
-
-std::string alut_error_append(ALenum num, std::string msg) {
-       const char *error = alutGetErrorString(num);
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-       }
-       return msg;
-}
-
-}
-
 namespace blank {
 
-AlutError::AlutError(ALenum num)
-: std::runtime_error(alutGetErrorString(num)) {
-
-}
-
-AlutError::AlutError(ALenum num, const std::string &msg)
-: std::runtime_error(alut_error_append(num, msg)) {
-
-}
-
-
-NetError::NetError()
-: std::runtime_error(SDLNet_GetError()) {
-
-}
-
-NetError::NetError(const std::string &msg)
-: std::runtime_error(net_error_append(msg)) {
-
-}
-
-
-SDLError::SDLError()
-: std::runtime_error(SDL_GetError()) {
-
-}
-
-SDLError::SDLError(const std::string &msg)
-: std::runtime_error(sdl_error_append(msg)) {
-
-}
-
-
 InitSDL::InitSDL() {
        if (SDL_Init(SDL_INIT_EVENTS) != 0) {
                throw SDLError("SDL_Init(SDL_INIT_EVENTS)");
 
 #ifndef BLANK_APP_INIT_HPP_
 #define BLANK_APP_INIT_HPP_
 
+#include "error.hpp"
+
 #include <al.h>
 #include <SDL.h>
-#include <stdexcept>
-#include <string>
 
 
 namespace blank {
 
-class AlutError
-: public std::runtime_error {
-
-public:
-       explicit AlutError(ALenum);
-       AlutError(ALenum, const std::string &);
-
-};
-
-class SDLError
-: public std::runtime_error {
-
-public:
-       SDLError();
-       explicit SDLError(const std::string &);
-
-};
-
-class NetError
-: public std::runtime_error {
-
-public:
-       NetError();
-       explicit NetError(const std::string &);
-
-};
-
-
 class InitSDL {
 
 public:
 
 #include "Process.hpp"
 
+#include "error.hpp"
+
 #ifdef _WIN32
 #  include <tchar.h>
 #  include <windows.h>
        argv[args.size()] = nullptr;
 
        if (pipe(fd_in) != 0) {
-               throw runtime_error("failed to open pipe for child process' stdin");
+               throw SysError("failed to open pipe for child process' stdin");
        }
        if (pipe(fd_out) != 0) {
-               throw runtime_error("failed to open pipe for child process' stdout");
+               throw SysError("failed to open pipe for child process' stdout");
        }
        if (pipe(fd_err) != 0) {
-               throw runtime_error("failed to open pipe for child process' stderr");
+               throw SysError("failed to open pipe for child process' stderr");
        }
 
        pid = fork();
        if (pid == -1) {
-               throw runtime_error("fork");
+               throw SysError("fork");
        } else if (pid == 0) {
 
                if (dup2(fd_in[0], STDIN_FILENO) == -1) {
                if (errno == EAGAIN) {
                        return 0;
                } else {
-                       throw runtime_error("failed to write to child process' input stream");
+                       throw SysError("failed to write to child process' input stream");
                }
        }
        return written;
                if (errno == EAGAIN) {
                        return 0;
                } else {
-                       throw runtime_error("failed to read from child process' output stream");
+                       throw SysError("failed to read from child process' output stream");
                }
        }
        return ret;
                if (errno == EAGAIN) {
                        return 0;
                } else {
-                       throw runtime_error("failed to read from child process' error stream");
+                       throw SysError("failed to read from child process' error stream");
                }
        }
        return ret;
 #else
        if (kill(pid, SIGTERM) == -1) {
 #endif
-               throw runtime_error("failed to terminate child process");
+               throw SysError("failed to terminate child process");
        }
 }
 
                int status;
                int result = waitpid(pid, &status, 0);
                if (result == -1) {
-                       throw runtime_error("error waiting on child process");
+                       throw SysError("error waiting on child process");
                }
                if (result == pid && WIFEXITED(status)) {
                        return WEXITSTATUS(status);
 
 #include "Client.hpp"
 #include "NetworkedInput.hpp"
 
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 #include "../geometry/distance.hpp"
 #include "../io/WorldSave.hpp"
 #include "../net/Packet.hpp"
 
 #include "TextureBase.hpp"
 #include "Viewport.hpp"
 
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 
 #include <algorithm>
 #include <cstring>
 Font::Font(const char *src, int size, long index)
 : handle(TTF_OpenFontIndex(src, size, index)) {
        if (!handle) {
-               throw std::runtime_error(TTF_GetError());
+               throw TTFError("TTF_OpenFontIndex");
        }
 }
 
 glm::ivec2 Font::TextSize(const char *text) const {
        glm::ivec2 size;
        if (TTF_SizeUTF8(handle, text, &size.x, &size.y) != 0) {
-               throw std::runtime_error(TTF_GetError());
+               throw TTFError("TTF_SizeUTF8");
        }
        return size;
 }
 void Font::Render(const char *text, Texture &tex) const {
        SDL_Surface *srf = TTF_RenderUTF8_Blended(handle, text, { 0xFF, 0xFF, 0xFF, 0xFF });
        if (!srf) {
-               throw std::runtime_error(TTF_GetError());
+               throw TTFError("TTF_RenderUTF8_Blended");
        }
        tex.Bind();
        tex.Data(*srf, false);
 
 #include "ArrayTexture.hpp"
 #include "CubeMap.hpp"
 #include "Texture.hpp"
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 
 #include <algorithm>
 #include <iostream>
 #include <glm/gtc/type_ptr.hpp>
 
 
-namespace {
-
-void gl_error(std::string msg) {
-       const GLubyte *errBegin = gluErrorString(glGetError());
-       if (errBegin && *errBegin != '\0') {
-               const GLubyte *errEnd = errBegin;
-               while (*errEnd != '\0') {
-                       ++errEnd;
-               }
-               msg += ": ";
-               msg.append(errBegin, errEnd);
-       }
-       throw std::runtime_error(msg);
-}
-
-}
-
 namespace blank {
 
 Shader::Shader(GLenum type)
 : handle(glCreateShader(type)) {
        if (handle == 0) {
-               gl_error("glCreateShader");
+               throw GLError("glCreateShader");
        }
 }
 
 Program::Program()
 : handle(glCreateProgram()) {
        if (handle == 0) {
-               gl_error("glCreateProgram");
+               throw GLError("glCreateProgram");
        }
 }
 
 
 #include "SkyBox.hpp"
 #include "Viewport.hpp"
 
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 #include "../geometry/const.hpp"
 
 #include <GL/glew.h>
 
 
 #include "filesystem.hpp"
 #include "TokenStreamReader.hpp"
+#include "../app/error.hpp"
 
 #include <cctype>
 #include <cstring>
                // check if it's because of a missing path component
                if (errno != ENOENT) {
                        // nope, fatal
-                       throw runtime_error(strerror(errno));
+                       throw SysError();
                }
                string dir_path(path);
                dir_path.erase(dir_path.find_last_of("\\/"));
 
 #include "filesystem.hpp"
 
+#include "../app/error.hpp"
+
 #include <cerrno>
 #include <cstdio>
 #include <cstdlib>
        char tmpl[] = "blank.XXXXXX";
        const char *name = mkdtemp(tmpl);
        if (!name) {
-               throw runtime_error("unable to create temporary directory");
+               throw SysError("unable to create temporary directory");
        }
        path = name;
 #else
 
 #include "io.hpp"
 #include "Packet.hpp"
 
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 #include "../geometry/const.hpp"
 #include "../model/Model.hpp"
 #include "../world/Entity.hpp"
 
 #include "tcp.hpp"
 
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 
 #include <stdexcept>
 
 
 #include "ChunkTransmitter.hpp"
 #include "Server.hpp"
 
-#include "../app/init.hpp"
+#include "../app/error.hpp"
 #include "../geometry/distance.hpp"
 #include "../io/WorldSave.hpp"
 #include "../model/Model.hpp"
 
 #include "../app/Config.hpp"
 #include "../app/Environment.hpp"
 #include "../app/FrameCounter.hpp"
-#include "../app/init.hpp"
 #include "../audio/Audio.hpp"
 #include "../audio/SoundBank.hpp"
 #include "../geometry/distance.hpp"