Blob Blame History Raw
From d2e39d2f50684151490da446156622e69dd84a48 Mon Sep 17 00:00:00 2001
From: heinrich5991 <heinrich5991@gmail.com>
Date: Mon, 26 Nov 2018 20:47:20 +0100
Subject: [PATCH 1/2] Add CMake

(cherry picked from commit ee2afdac33f43d96a457bcf692a817489d6f896e)
---
 .gitignore                                    |   40 +-
 CMakeLists.txt                                | 1824 +++++++++++++++++
 bam.lua                                       |    2 +
 cmake/FindFreetype.cmake                      |   37 +
 cmake/FindPnglite.cmake                       |   46 +
 cmake/FindSDL2.cmake                          |   48 +
 cmake/FindWavpack.cmake                       |   52 +
 cmake/FindZLIB.cmake                          |   50 +
 cmake/toolchains/darwin.toolchain             |   11 +
 cmake/toolchains/mingw32.toolchain            |   10 +
 cmake/toolchains/mingw64.toolchain            |   10 +
 other/bundle/client/Info.plist.in             |   24 +
 other/bundle/client/PkgInfo                   |    1 +
 other/bundle/server/Info.plist.in             |   20 +
 other/bundle/server/PkgInfo                   |    1 +
 other/freetype/freetype.lua                   |    4 +-
 .../icons/{Teeworlds.icns => teeworlds.icns}  |  Bin
 other/icons/{Teeworlds.ico => teeworlds.ico}  |  Bin
 other/icons/teeworlds.rc                      |    1 +
 ...{Teeworlds_srv.icns => teeworlds_srv.icns} |  Bin
 .../{Teeworlds_srv.ico => teeworlds_srv.ico}  |  Bin
 other/icons/teeworlds_srv.rc                  |    1 +
 other/icons/teeworlds_srv_cl.rc               |    2 +-
 other/icons/teeworlds_srv_gcc.rc              |    2 +-
 other/sdl/sdl.lua                             |    4 +-
 scripts/darwin_change_dylib.py                |   63 +
 scripts/dmg.py                                |  106 +
 scripts/git_revision.py                       |   21 +
 src/base/system.c                             |   13 +-
 src/base/system.h                             |   15 +
 src/engine/client/backend_sdl.cpp             |    2 +-
 src/engine/client/graphics_threaded.cpp       |    3 +-
 src/engine/client/sound.cpp                   |   78 +-
 src/engine/client/sound.h                     |    4 -
 src/osxlaunch/server.mm                       |  112 +
 35 files changed, 2570 insertions(+), 37 deletions(-)
 create mode 100644 CMakeLists.txt
 create mode 100644 cmake/FindFreetype.cmake
 create mode 100644 cmake/FindPnglite.cmake
 create mode 100644 cmake/FindSDL2.cmake
 create mode 100644 cmake/FindWavpack.cmake
 create mode 100644 cmake/FindZLIB.cmake
 create mode 100644 cmake/toolchains/darwin.toolchain
 create mode 100644 cmake/toolchains/mingw32.toolchain
 create mode 100644 cmake/toolchains/mingw64.toolchain
 create mode 100644 other/bundle/client/Info.plist.in
 create mode 100644 other/bundle/client/PkgInfo
 create mode 100644 other/bundle/server/Info.plist.in
 create mode 100644 other/bundle/server/PkgInfo
 rename other/icons/{Teeworlds.icns => teeworlds.icns} (100%)
 rename other/icons/{Teeworlds.ico => teeworlds.ico} (100%)
 create mode 100644 other/icons/teeworlds.rc
 rename other/icons/{Teeworlds_srv.icns => teeworlds_srv.icns} (100%)
 rename other/icons/{Teeworlds_srv.ico => teeworlds_srv.ico} (100%)
 create mode 100644 other/icons/teeworlds_srv.rc
 create mode 100644 scripts/darwin_change_dylib.py
 create mode 100644 scripts/dmg.py
 create mode 100644 scripts/git_revision.py
 create mode 100644 src/osxlaunch/server.mm

diff --git a/.gitignore b/.gitignore
index 2fbc9201f..3d102c096 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,12 @@
-/bam
 /.bam
-/config.lua
+/bam
 /build
-/other/*/lib
+/config.lua
+/objs
 /other/*/include
+/other/*/lib
 __pycache__/
+*.dll
 *.pyc
 *.pyo
 scripts/work/
@@ -12,3 +14,35 @@ scripts/work/
 /freetype.dll
 /autoexec.cfg
 _test.exe
+Info.plist
+
+crapnet*
+fake_server*
+map_resave*
+map_version*
+mastersrv*
+packetgen*
+teeworlds*
+teeworlds_srv*
+versionsrv*
+
+# CMake
+data
+generated
+
+.ninja_deps
+.ninja_log
+CMakeCache.txt
+CMakeFiles
+CMakeSettings*
+CPackConfig.cmake
+CPackSourceConfig.cmake
+Debug
+Makefile
+Release
+_CPack_Packages/
+build.ninja
+cmake_install.cmake
+install_manifest.txt
+pack_*/
+rules.ninja
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 000000000..f5be54e07
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,1824 @@
+cmake_minimum_required(VERSION 2.8)
+
+file(STRINGS src/game/version.h VERSION_LINE
+  LIMIT_COUNT 1
+  REGEX GAME_RELEASE_VERSION
+)
+
+if(VERSION_LINE MATCHES "\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\"")
+  set(VERSION_MAJOR ${CMAKE_MATCH_1})
+  set(VERSION_MINOR ${CMAKE_MATCH_2})
+  set(VERSION_PATCH ${CMAKE_MATCH_3})
+elseif(VERSION_LINE MATCHES "\"([0-9]+)\\.([0-9]+)\"")
+  set(VERSION_MAJOR ${CMAKE_MATCH_1})
+  set(VERSION_MINOR ${CMAKE_MATCH_2})
+  set(VERSION_PATCH "0")
+else()
+  message(FATAL_ERROR "Couldn't parse version from src/game/version.h")
+endif()
+
+if(POLICY CMP0048)
+  cmake_policy(SET CMP0048 NEW)
+  project(teeworlds VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
+else()
+  project(teeworlds)
+  set(PROJECT_VERSION_MAJOR ${VERSION_MAJOR})
+  set(PROJECT_VERSION_MINOR ${VERSION_MINOR})
+  set(PROJECT_VERSION_PATCH ${VERSION_PATCH})
+  set(PROJECT_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
+endif()
+
+set(ORIGINAL_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
+set(ORIGINAL_CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES})
+set(ORIGINAL_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
+set(OWN_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
+set(CMAKE_MODULE_PATH ${OWN_CMAKE_MODULE_PATH})
+
+if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+  set(TARGET_BITS "64")
+elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  set(TARGET_BITS "32")
+endif()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+  set(TARGET_OS "windows")
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+  set(TARGET_OS "linux")
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  set(TARGET_OS "mac")
+endif()
+
+include(CheckCCompilerFlag)
+include(CheckCXXCompilerFlag)
+include(CheckSymbolExists)
+
+check_symbol_exists(__i386 "" TARGET_ARCH_X86_i386)
+if(TARGET_ARCH_X86_i386)
+  set(TARGET_ARCH x86)
+else()
+  set(TARGET_ARCH)
+endif()
+
+set(AUTO_DEPENDENCIES_DEFAULT OFF)
+if(TARGET_OS STREQUAL "windows")
+  set(AUTO_DEPENDENCIES_DEFAULT ON)
+endif()
+
+option(CLIENT "Compile client" ON)
+option(PREFER_BUNDLED_LIBS "Prefer bundled libraries over system libraries" ${AUTO_DEPENDENCIES_DEFAULT})
+option(DEV "Don't generate stuff necessary for packaging" OFF)
+
+# Set the default build type to Release
+if(NOT(CMAKE_BUILD_TYPE))
+  if(DEV)
+    set(CMAKE_BUILD_TYPE Release)
+  else()
+    set(CMAKE_BUILD_TYPE Debug)
+  endif()
+endif()
+
+set(DBG $<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>)
+
+set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
+  src/game/version.h
+)
+
+set(SERVER_EXECUTABLE teeworlds_srv CACHE STRING "Name of the built server executable")
+set(CLIENT_EXECUTABLE teeworlds CACHE STRING "Name of the build client executable")
+
+########################################################################
+# Compiler flags
+########################################################################
+
+function(add_c_compiler_flag_if_supported VARIABLE FLAG)
+  if(ARGC GREATER 2)
+    set(CHECKED_FLAG "${ARGV2}")
+  else()
+    set(CHECKED_FLAG "${FLAG}")
+  endif()
+  string(REGEX REPLACE "[^A-Za-z0-9]" "_" CONFIG_VARIABLE "FLAG_SUPPORTED${CHECKED_FLAG}")
+  check_c_compiler_flag("${CHECKED_FLAG}" ${CONFIG_VARIABLE})
+  if(${CONFIG_VARIABLE})
+    if(${VARIABLE})
+      set("${VARIABLE}" "${${VARIABLE}};${FLAG}" PARENT_SCOPE)
+    else()
+      set("${VARIABLE}" "${FLAG}" PARENT_SCOPE)
+    endif()
+  endif()
+endfunction()
+
+if(NOT MSVC)
+  # Protect the stack pointer.
+  # -fstack-protector-all doesn't work on MinGW.
+  add_c_compiler_flag_if_supported(OUR_FLAGS -fstack-protector-all)
+
+  # Inaccurate floating point numbers cause problems on mingw-w64-gcc when
+  # compiling for x86, might cause problems elsewhere. So don't store floats
+  # in registers but keep them at higher accuracy.
+
+  if(TARGET_ARCH STREQUAL "x86")
+    add_c_compiler_flag_if_supported(OUR_FLAGS -ffloat-store)
+  endif()
+
+  if(TARGET_OS STREQUAL "mac")
+    add_c_compiler_flag_if_supported(OUR_FLAGS -stdlib=libc++)
+    add_c_compiler_flag_if_supported(OUR_FLAGS -mmacosx-version-min=10.7)
+  endif()
+
+  add_c_compiler_flag_if_supported(OUR_FLAGS_OWN -Wall)
+  if(CMAKE_VERSION VERSION_GREATER 3.3 OR CMAKE_VERSION VERSION_EQUAL 3.3)
+    add_c_compiler_flag_if_supported(OUR_FLAGS_OWN
+      $<$<COMPILE_LANGUAGE:C>:-Wdeclaration-after-statement>
+      -Wdeclaration-after-statement
+    )
+  endif()
+  add_c_compiler_flag_if_supported(OUR_FLAGS_OWN -Wextra)
+  add_c_compiler_flag_if_supported(OUR_FLAGS_OWN -Wno-unused-parameter)
+  add_c_compiler_flag_if_supported(OUR_FLAGS_OWN -Wno-missing-field-initializers)
+  add_c_compiler_flag_if_supported(OUR_FLAGS_OWN -Wformat=2) # Warn about format strings.
+  add_c_compiler_flag_if_supported(OUR_FLAGS_DEP -Wno-implicit-function-declaration)
+endif()
+
+if(NOT MSVC)
+  check_c_compiler_flag("-O2;-Wp,-Werror;-D_FORTIFY_SOURCE=2" DEFINE_FORTIFY_SOURCE) # Some distributions define _FORTIFY_SOURCE by themselves.
+endif()
+
+########################################################################
+# COMMON FUNCTIONS
+########################################################################
+
+function(set_glob VAR GLOBBING EXTS DIRECTORY) # ...
+  set(GLOBS)
+  foreach(ext ${EXTS})
+    list(APPEND GLOBS "${DIRECTORY}/*.${ext}")
+  endforeach()
+  file(${GLOBBING} GLOB_RESULT ${GLOBS})
+  list(SORT GLOB_RESULT)
+  set(FILES)
+  foreach(file ${ARGN})
+    list(APPEND FILES "${PROJECT_SOURCE_DIR}/${DIRECTORY}/${file}")
+  endforeach()
+
+  if(NOT FILES STREQUAL GLOB_RESULT)
+    message(AUTHOR_WARNING "${VAR} does not contain every file from directory ${DIRECTORY}")
+    set(LIST_BUT_NOT_GLOB)
+    if(POLICY CMP0057)
+      cmake_policy(SET CMP0057 NEW)
+      foreach(file ${FILES})
+        if(NOT file IN_LIST GLOB_RESULT)
+          list(APPEND LIST_BUT_NOT_GLOB ${file})
+        endif()
+      endforeach()
+      if(LIST_BUT_NOT_GLOB)
+        message(AUTHOR_WARNING "Entries only present in ${VAR}: ${LIST_BUT_NOT_GLOB}")
+      endif()
+      set(GLOB_BUT_NOT_LIST)
+      foreach(file ${GLOB_RESULT})
+        if(NOT file IN_LIST FILES)
+          list(APPEND GLOB_BUT_NOT_LIST ${file})
+        endif()
+      endforeach()
+      if(GLOB_BUT_NOT_LIST)
+        message(AUTHOR_WARNING "Entries only present in ${DIRECTORY}: ${GLOB_BUT_NOT_LIST}")
+      endif()
+    endif()
+  endif()
+
+  set(${VAR} ${FILES} PARENT_SCOPE)
+endfunction()
+
+function(set_src VAR GLOBBING DIRECTORY) # ...
+  set_glob(${VAR} ${GLOBBING} "c;cpp;h" ${DIRECTORY} ${ARGN})
+  set(${VAR} ${${VAR}} PARENT_SCOPE)
+endfunction()
+
+########################################################################
+# INITIALIZE TARGET LISTS
+########################################################################
+
+set(TARGETS_OWN)
+set(TARGETS_DEP)
+
+set(TARGETS_LINK) # Targets with a linking stage.
+
+########################################################################
+# DEPENDENCIES
+########################################################################
+
+function(set_extra_dirs_lib VARIABLE NAME)
+  set("PATHS_${VARIABLE}_LIBDIR" PARENT_SCOPE)
+  set("HINTS_${VARIABLE}_LIBDIR" PARENT_SCOPE)
+  if(PREFER_BUNDLED_LIBS)
+    set(TYPE HINTS)
+  else()
+    set(TYPE PATHS)
+  endif()
+  if(TARGET_BITS AND TARGET_OS)
+    set(DIR "other/${NAME}/${TARGET_OS}/lib${TARGET_BITS}")
+    set("${TYPE}_${VARIABLE}_LIBDIR" "${DIR}" PARENT_SCOPE)
+    set("EXTRA_${VARIABLE}_LIBDIR" "${DIR}" PARENT_SCOPE)
+  endif()
+endfunction()
+
+function(set_extra_dirs_include VARIABLE NAME LIBRARY)
+  set("PATHS_${VARIABLE}_INCLUDEDIR" PARENT_SCOPE)
+  set("HINTS_${VARIABLE}_INCLUDEDIR" PARENT_SCOPE)
+  is_bundled(IS_BUNDLED "${LIBRARY}")
+  if(IS_BUNDLED)
+    set("HINTS_${VARIABLE}_INCLUDEDIR" "other/${NAME}/include" "other/${NAME}/include/${TARGET_OS}" PARENT_SCOPE)
+  endif()
+endfunction()
+
+if(CMAKE_CROSSCOMPILING)
+  set(CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH NO_CMAKE_SYSTEM_PATH)
+else()
+  set(CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH)
+endif()
+
+function(is_bundled VARIABLE PATH)
+  if(PATH)
+    string(FIND "${PATH}" "${PROJECT_SOURCE_DIR}" LOCAL_PATH_POS)
+    if(LOCAL_PATH_POS EQUAL 0 AND TARGET_BITS AND TARGET_OS)
+      set("${VARIABLE}" ON PARENT_SCOPE)
+    else()
+      set("${VARIABLE}" OFF PARENT_SCOPE)
+    endif()
+  else()
+    set("${VARIABLE}" OFF PARENT_SCOPE)
+  endif()
+endfunction()
+
+if(NOT CMAKE_CROSSCOMPILING)
+  # Check for PkgConfig once so all the other `find_package` calls can do it
+  # quietly.
+  find_package(PkgConfig)
+endif()
+find_package(ZLIB)
+find_package(Freetype)
+find_package(Git)
+find_package(Pnglite)
+find_package(PythonInterp)
+find_package(SDL2)
+find_package(Threads)
+find_package(Wavpack)
+
+
+if(TARGET_OS AND TARGET_OS STREQUAL "mac")
+  find_program(CMAKE_OTOOL otool)
+  find_program(DMG dmg)
+  find_program(HFSPLUS hfsplus)
+  find_program(NEWFS_HFS newfs_hfs)
+  if(DMG AND HFSPLUS AND NEWFS_HFS)
+    set(DMGTOOLS_FOUND ON)
+  else()
+    set(DMGTOOLS_FOUND OFF)
+  endif()
+
+  find_program(HDIUTIL hdiutil)
+endif()
+
+message(STATUS "******** Teeworlds ********")
+message(STATUS "Target OS: ${TARGET_OS} ${TARGET_BITS}bit")
+message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
+message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+
+message(STATUS "Dependencies:")
+function(show_dependency_status OUTPUT_NAME NAME)
+  if(${NAME}_FOUND)
+    if(${NAME}_BUNDLED)
+      message(STATUS " * ${OUTPUT_NAME} not found (using bundled version)")
+    else()
+      message(STATUS " * ${OUTPUT_NAME} found")
+    endif()
+  else()
+    message(STATUS " * ${OUTPUT_NAME} not found")
+  endif()
+endfunction()
+
+if(TARGET_OS AND TARGET_OS STREQUAL "mac")
+  show_dependency_status("Dmg tools" DMGTOOLS)
+endif()
+show_dependency_status("Freetype" FREETYPE)
+if(TARGET_OS AND TARGET_OS STREQUAL "mac")
+  show_dependency_status("Hdiutil" HDIUTIL)
+endif()
+show_dependency_status("Pnglite" PNGLITE)
+show_dependency_status("PythonInterp" PYTHONINTERP)
+show_dependency_status("SDL2" SDL2)
+show_dependency_status("Wavpack" WAVPACK)
+show_dependency_status("Zlib" ZLIB)
+
+if(NOT(PYTHONINTERP_FOUND))
+  message(SEND_ERROR "You must install Python to compile Teeworlds")
+endif()
+
+if(CLIENT AND NOT(FREETYPE_FOUND))
+  message(SEND_ERROR "You must install Freetype to compile the Teeworlds client")
+endif()
+if(CLIENT AND NOT(SDL2_FOUND))
+  message(SEND_ERROR "You must install SDL2 to compile the Teeworlds client")
+endif()
+
+if(TARGET_OS STREQUAL "windows")
+  set(PLATFORM_CLIENT)
+  set(PLATFORM_CLIENT_LIBS opengl32 glu32 winmm)
+  set(PLATFORM_LIBS ws2_32) # Windows sockets
+elseif(TARGET_OS STREQUAL "mac")
+  find_library(CARBON Carbon)
+  find_library(COCOA Cocoa)
+  find_library(OPENGL OpenGL)
+  set(PLATFORM_CLIENT
+    src/osxlaunch/client.h
+    src/osxlaunch/client.m
+  )
+  set(PLATFORM_CLIENT_LIBS ${COCOA} ${OPENGL})
+  set(PLATFORM_LIBS ${CARBON})
+else()
+  set(PLATFORM_CLIENT)
+  set(PLATFORM_CLIENT_LIBS GL GLU X11)
+  if(TARGET_OS STREQUAL "linux")
+    set(PLATFORM_LIBS rt) # clock_gettime for glibc < 2.17
+  else()
+    set(PLATFORM_LIBS)
+  endif()
+endif()
+
+########################################################################
+# DEPENDENCY COMPILATION
+########################################################################
+
+set_src(DEP_JSON_SRC GLOB src/engine/external/json-parser json.c json.h)
+add_library(json EXCLUDE_FROM_ALL OBJECT ${DEP_JSON_SRC})
+
+set_src(DEP_MD5_SRC GLOB src/engine/external/md5 md5.c md5.h)
+add_library(md5 EXCLUDE_FROM_ALL OBJECT ${DEP_MD5_SRC})
+
+list(APPEND TARGETS_DEP json md5)
+set(DEP_MD5 $<TARGET_OBJECTS:json> $<TARGET_OBJECTS:md5>)
+
+########################################################################
+# DATA
+########################################################################
+
+set_glob(DATA GLOB_RECURSE "json;map;png;rules;ttf;txt;wv" datasrc
+  audio/foley_body_impact-01.wv
+  audio/foley_body_impact-02.wv
+  audio/foley_body_impact-03.wv
+  audio/foley_body_splat-01.wv
+  audio/foley_body_splat-02.wv
+  audio/foley_body_splat-03.wv
+  audio/foley_body_splat-04.wv
+  audio/foley_dbljump-01.wv
+  audio/foley_dbljump-02.wv
+  audio/foley_dbljump-03.wv
+  audio/foley_foot_left-01.wv
+  audio/foley_foot_left-02.wv
+  audio/foley_foot_left-03.wv
+  audio/foley_foot_left-04.wv
+  audio/foley_foot_right-01.wv
+  audio/foley_foot_right-02.wv
+  audio/foley_foot_right-03.wv
+  audio/foley_foot_right-04.wv
+  audio/foley_land-01.wv
+  audio/foley_land-02.wv
+  audio/foley_land-03.wv
+  audio/foley_land-04.wv
+  audio/hook_attach-01.wv
+  audio/hook_attach-02.wv
+  audio/hook_attach-03.wv
+  audio/hook_loop-01.wv
+  audio/hook_loop-02.wv
+  audio/hook_noattach-01.wv
+  audio/hook_noattach-02.wv
+  audio/hook_noattach-03.wv
+  audio/music_menu.wv
+  audio/sfx_ctf_cap_pl.wv
+  audio/sfx_ctf_drop.wv
+  audio/sfx_ctf_grab_en.wv
+  audio/sfx_ctf_grab_pl.wv
+  audio/sfx_ctf_rtn.wv
+  audio/sfx_hit_strong-01.wv
+  audio/sfx_hit_strong-02.wv
+  audio/sfx_hit_weak-01.wv
+  audio/sfx_hit_weak-02.wv
+  audio/sfx_hit_weak-03.wv
+  audio/sfx_msg-client.wv
+  audio/sfx_msg-highlight.wv
+  audio/sfx_msg-server.wv
+  audio/sfx_pickup_arm-01.wv
+  audio/sfx_pickup_arm-02.wv
+  audio/sfx_pickup_arm-03.wv
+  audio/sfx_pickup_arm-04.wv
+  audio/sfx_pickup_gun.wv
+  audio/sfx_pickup_hrt-01.wv
+  audio/sfx_pickup_hrt-02.wv
+  audio/sfx_pickup_launcher.wv
+  audio/sfx_pickup_ninja.wv
+  audio/sfx_pickup_sg.wv
+  audio/sfx_skid-01.wv
+  audio/sfx_skid-02.wv
+  audio/sfx_skid-03.wv
+  audio/sfx_skid-04.wv
+  audio/sfx_spawn_wpn-01.wv
+  audio/sfx_spawn_wpn-02.wv
+  audio/sfx_spawn_wpn-03.wv
+  audio/vo_teefault_cry-01.wv
+  audio/vo_teefault_cry-02.wv
+  audio/vo_teefault_ninja-01.wv
+  audio/vo_teefault_ninja-02.wv
+  audio/vo_teefault_ninja-03.wv
+  audio/vo_teefault_ninja-04.wv
+  audio/vo_teefault_pain_long-01.wv
+  audio/vo_teefault_pain_long-02.wv
+  audio/vo_teefault_pain_short-01.wv
+  audio/vo_teefault_pain_short-02.wv
+  audio/vo_teefault_pain_short-03.wv
+  audio/vo_teefault_pain_short-04.wv
+  audio/vo_teefault_pain_short-05.wv
+  audio/vo_teefault_pain_short-06.wv
+  audio/vo_teefault_pain_short-07.wv
+  audio/vo_teefault_pain_short-08.wv
+  audio/vo_teefault_pain_short-09.wv
+  audio/vo_teefault_pain_short-10.wv
+  audio/vo_teefault_pain_short-11.wv
+  audio/vo_teefault_pain_short-12.wv
+  audio/vo_teefault_sledge-01.wv
+  audio/vo_teefault_sledge-02.wv
+  audio/vo_teefault_sledge-03.wv
+  audio/vo_teefault_spawn-01.wv
+  audio/vo_teefault_spawn-02.wv
+  audio/vo_teefault_spawn-03.wv
+  audio/vo_teefault_spawn-04.wv
+  audio/vo_teefault_spawn-05.wv
+  audio/vo_teefault_spawn-06.wv
+  audio/vo_teefault_spawn-07.wv
+  audio/wp_flump_explo-01.wv
+  audio/wp_flump_explo-02.wv
+  audio/wp_flump_explo-03.wv
+  audio/wp_flump_launch-01.wv
+  audio/wp_flump_launch-02.wv
+  audio/wp_flump_launch-03.wv
+  audio/wp_gun_fire-01.wv
+  audio/wp_gun_fire-02.wv
+  audio/wp_gun_fire-03.wv
+  audio/wp_hammer_hit-01.wv
+  audio/wp_hammer_hit-02.wv
+  audio/wp_hammer_hit-03.wv
+  audio/wp_hammer_swing-01.wv
+  audio/wp_hammer_swing-02.wv
+  audio/wp_hammer_swing-03.wv
+  audio/wp_laser_bnce-01.wv
+  audio/wp_laser_bnce-02.wv
+  audio/wp_laser_bnce-03.wv
+  audio/wp_laser_fire-01.wv
+  audio/wp_laser_fire-02.wv
+  audio/wp_laser_fire-03.wv
+  audio/wp_ninja_attack-01.wv
+  audio/wp_ninja_attack-02.wv
+  audio/wp_ninja_attack-03.wv
+  audio/wp_ninja_attack-04.wv
+  audio/wp_ninja_hit-01.wv
+  audio/wp_ninja_hit-02.wv
+  audio/wp_ninja_hit-03.wv
+  audio/wp_ninja_hit-04.wv
+  audio/wp_noammo-01.wv
+  audio/wp_noammo-02.wv
+  audio/wp_noammo-03.wv
+  audio/wp_noammo-04.wv
+  audio/wp_noammo-05.wv
+  audio/wp_shotty_fire-01.wv
+  audio/wp_shotty_fire-02.wv
+  audio/wp_shotty_fire-03.wv
+  audio/wp_switch-01.wv
+  audio/wp_switch-02.wv
+  audio/wp_switch-03.wv
+  countryflags/AD.png
+  countryflags/AE.png
+  countryflags/AF.png
+  countryflags/AG.png
+  countryflags/AI.png
+  countryflags/AL.png
+  countryflags/AM.png
+  countryflags/AO.png
+  countryflags/AR.png
+  countryflags/AS.png
+  countryflags/AT.png
+  countryflags/AU.png
+  countryflags/AW.png
+  countryflags/AX.png
+  countryflags/AZ.png
+  countryflags/BA.png
+  countryflags/BB.png
+  countryflags/BD.png
+  countryflags/BE.png
+  countryflags/BF.png
+  countryflags/BG.png
+  countryflags/BH.png
+  countryflags/BI.png
+  countryflags/BJ.png
+  countryflags/BL.png
+  countryflags/BM.png
+  countryflags/BN.png
+  countryflags/BO.png
+  countryflags/BR.png
+  countryflags/BS.png
+  countryflags/BT.png
+  countryflags/BW.png
+  countryflags/BY.png
+  countryflags/BZ.png
+  countryflags/CA.png
+  countryflags/CC.png
+  countryflags/CD.png
+  countryflags/CF.png
+  countryflags/CG.png
+  countryflags/CH.png
+  countryflags/CI.png
+  countryflags/CK.png
+  countryflags/CL.png
+  countryflags/CM.png
+  countryflags/CN.png
+  countryflags/CO.png
+  countryflags/CR.png
+  countryflags/CU.png
+  countryflags/CV.png
+  countryflags/CW.png
+  countryflags/CX.png
+  countryflags/CY.png
+  countryflags/CZ.png
+  countryflags/DE.png
+  countryflags/DJ.png
+  countryflags/DK.png
+  countryflags/DM.png
+  countryflags/DO.png
+  countryflags/DZ.png
+  countryflags/EC.png
+  countryflags/EE.png
+  countryflags/EG.png
+  countryflags/EH.png
+  countryflags/ER.png
+  countryflags/ES.png
+  countryflags/ET.png
+  countryflags/FI.png
+  countryflags/FJ.png
+  countryflags/FK.png
+  countryflags/FM.png
+  countryflags/FO.png
+  countryflags/FR.png
+  countryflags/GA.png
+  countryflags/GB.png
+  countryflags/GD.png
+  countryflags/GE.png
+  countryflags/GF.png
+  countryflags/GG.png
+  countryflags/GH.png
+  countryflags/GI.png
+  countryflags/GL.png
+  countryflags/GM.png
+  countryflags/GN.png
+  countryflags/GP.png
+  countryflags/GQ.png
+  countryflags/GR.png
+  countryflags/GS.png
+  countryflags/GT.png
+  countryflags/GU.png
+  countryflags/GW.png
+  countryflags/GY.png
+  countryflags/HK.png
+  countryflags/HN.png
+  countryflags/HR.png
+  countryflags/HT.png
+  countryflags/HU.png
+  countryflags/ID.png
+  countryflags/IE.png
+  countryflags/IL.png
+  countryflags/IM.png
+  countryflags/IN.png
+  countryflags/IO.png
+  countryflags/IQ.png
+  countryflags/IR.png
+  countryflags/IS.png
+  countryflags/IT.png
+  countryflags/JE.png
+  countryflags/JM.png
+  countryflags/JO.png
+  countryflags/JP.png
+  countryflags/KE.png
+  countryflags/KG.png
+  countryflags/KH.png
+  countryflags/KI.png
+  countryflags/KM.png
+  countryflags/KN.png
+  countryflags/KP.png
+  countryflags/KR.png
+  countryflags/KW.png
+  countryflags/KY.png
+  countryflags/KZ.png
+  countryflags/LA.png
+  countryflags/LB.png
+  countryflags/LC.png
+  countryflags/LI.png
+  countryflags/LK.png
+  countryflags/LR.png
+  countryflags/LS.png
+  countryflags/LT.png
+  countryflags/LU.png
+  countryflags/LV.png
+  countryflags/LY.png
+  countryflags/MA.png
+  countryflags/MC.png
+  countryflags/MD.png
+  countryflags/ME.png
+  countryflags/MF.png
+  countryflags/MG.png
+  countryflags/MH.png
+  countryflags/MK.png
+  countryflags/ML.png
+  countryflags/MM.png
+  countryflags/MN.png
+  countryflags/MO.png
+  countryflags/MP.png
+  countryflags/MQ.png
+  countryflags/MR.png
+  countryflags/MS.png
+  countryflags/MT.png
+  countryflags/MU.png
+  countryflags/MV.png
+  countryflags/MW.png
+  countryflags/MX.png
+  countryflags/MY.png
+  countryflags/MZ.png
+  countryflags/NA.png
+  countryflags/NC.png
+  countryflags/NE.png
+  countryflags/NF.png
+  countryflags/NG.png
+  countryflags/NI.png
+  countryflags/NL.png
+  countryflags/NO.png
+  countryflags/NP.png
+  countryflags/NR.png
+  countryflags/NU.png
+  countryflags/NZ.png
+  countryflags/OM.png
+  countryflags/PA.png
+  countryflags/PE.png
+  countryflags/PF.png
+  countryflags/PG.png
+  countryflags/PH.png
+  countryflags/PK.png
+  countryflags/PL.png
+  countryflags/PM.png
+  countryflags/PN.png
+  countryflags/PR.png
+  countryflags/PS.png
+  countryflags/PT.png
+  countryflags/PW.png
+  countryflags/PY.png
+  countryflags/QA.png
+  countryflags/RE.png
+  countryflags/RO.png
+  countryflags/RS.png
+  countryflags/RU.png
+  countryflags/RW.png
+  countryflags/SA.png
+  countryflags/SB.png
+  countryflags/SC.png
+  countryflags/SD.png
+  countryflags/SE.png
+  countryflags/SG.png
+  countryflags/SH.png
+  countryflags/SI.png
+  countryflags/SK.png
+  countryflags/SL.png
+  countryflags/SM.png
+  countryflags/SN.png
+  countryflags/SO.png
+  countryflags/SR.png
+  countryflags/SS.png
+  countryflags/ST.png
+  countryflags/SV.png
+  countryflags/SX.png
+  countryflags/SY.png
+  countryflags/SZ.png
+  countryflags/TC.png
+  countryflags/TD.png
+  countryflags/TF.png
+  countryflags/TG.png
+  countryflags/TH.png
+  countryflags/TJ.png
+  countryflags/TK.png
+  countryflags/TL.png
+  countryflags/TM.png
+  countryflags/TN.png
+  countryflags/TO.png
+  countryflags/TR.png
+  countryflags/TT.png
+  countryflags/TV.png
+  countryflags/TW.png
+  countryflags/TZ.png
+  countryflags/UA.png
+  countryflags/UG.png
+  countryflags/US.png
+  countryflags/UY.png
+  countryflags/UZ.png
+  countryflags/VA.png
+  countryflags/VC.png
+  countryflags/VE.png
+  countryflags/VG.png
+  countryflags/VI.png
+  countryflags/VN.png
+  countryflags/VU.png
+  countryflags/WF.png
+  countryflags/WS.png
+  countryflags/XBZ.png
+  countryflags/XCA.png
+  countryflags/XEN.png
+  countryflags/XES.png
+  countryflags/XGA.png
+  countryflags/XNI.png
+  countryflags/XSC.png
+  countryflags/XWA.png
+  countryflags/YE.png
+  countryflags/ZA.png
+  countryflags/ZM.png
+  countryflags/ZW.png
+  countryflags/default.png
+  countryflags/index.json
+  deadtee.png
+  editor/automap/grass_doodads.json
+  editor/automap/grass_main.json
+  editor/background.png
+  editor/checker.png
+  editor/cursor.png
+  editor/desert_main.rules
+  editor/entities.png
+  editor/grass_main.rules
+  editor/jungle_main.rules
+  editor/winter_main.rules
+  emoticons.png
+  fonts/DejaVuSans.ttf
+  game.png
+  languages/belarusian.json
+  languages/bosnian.json
+  languages/brazilian_portuguese.json
+  languages/breton.json
+  languages/bulgarian.json
+  languages/catalan.json
+  languages/chinese.json
+  languages/chuvash.json
+  languages/czech.json
+  languages/danish.json
+  languages/dutch.json
+  languages/esperanto.json
+  languages/estonian.json
+  languages/finnish.json
+  languages/french.json
+  languages/gaelic_scottish.json
+  languages/galician.json
+  languages/german.json
+  languages/greek.json
+  languages/hungarian.json
+  languages/index.json
+  languages/irish.json
+  languages/italian.json
+  languages/japanese.json
+  languages/korean.json
+  languages/kyrgyz.json
+  languages/license.txt
+  languages/lithuanian.json
+  languages/norwegian.json
+  languages/polish.json
+  languages/portuguese.json
+  languages/readme.txt
+  languages/romanian.json
+  languages/russian.json
+  languages/serbian.json
+  languages/simplified_chinese.json
+  languages/slovak.json
+  languages/slovenian.json
+  languages/spanish.json
+  languages/swedish.json
+  languages/turkish.json
+  languages/ukrainian.json
+  mapres/bg_cloud1.png
+  mapres/bg_cloud2.png
+  mapres/bg_cloud3.png
+  mapres/desert_doodads.png
+  mapres/desert_main.png
+  mapres/desert_mountains.png
+  mapres/desert_mountains2.png
+  mapres/desert_sun.png
+  mapres/generic_deathtiles.png
+  mapres/generic_lamps.png
+  mapres/generic_shadows.png
+  mapres/generic_unhookable.png
+  mapres/grass_doodads.png
+  mapres/grass_main.png
+  mapres/jungle_background.png
+  mapres/jungle_deathtiles.png
+  mapres/jungle_doodads.png
+  mapres/jungle_main.png
+  mapres/jungle_midground.png
+  mapres/jungle_unhookables.png
+  mapres/light.png
+  mapres/moon.png
+  mapres/mountains.png
+  mapres/snow.png
+  mapres/stars.png
+  mapres/sun.png
+  mapres/winter_doodads.png
+  mapres/winter_main.png
+  mapres/winter_mountains.png
+  mapres/winter_mountains2.png
+  mapres/winter_mountains3.png
+  maps/ctf1.map
+  maps/ctf2.map
+  maps/ctf3.map
+  maps/ctf4.map
+  maps/ctf5.map
+  maps/ctf6.map
+  maps/ctf7.map
+  maps/ctf8.map
+  maps/dm1.map
+  maps/dm2.map
+  maps/dm3.map
+  maps/dm6.map
+  maps/dm7.map
+  maps/dm8.map
+  maps/dm9.map
+  maps/license.txt
+  maps/lms1.map
+  maps/readme.txt
+  particles.png
+  skins/bluekitty.json
+  skins/bluestripe.json
+  skins/body/bear.png
+  skins/body/kitty.png
+  skins/body/standard.png
+  skins/body/x_ninja.png
+  skins/brownbear.json
+  skins/cammo.json
+  skins/cammostripes.json
+  skins/decoration/hair.png
+  skins/decoration/twinbopp.png
+  skins/decoration/unibop.png
+  skins/default.json
+  skins/eyes/standard.png
+  skins/eyes/standardreal.png
+  skins/eyes/x_ninja.png
+  skins/feet/standard.png
+  skins/hands/standard.png
+  skins/koala.json
+  skins/limekitty.json
+  skins/marking/bear.png
+  skins/marking/cammo1.png
+  skins/marking/cammo2.png
+  skins/marking/cammostripes.png
+  skins/marking/donny.png
+  skins/marking/duodonny.png
+  skins/marking/saddo.png
+  skins/marking/stripe.png
+  skins/marking/stripes.png
+  skins/marking/toptri.png
+  skins/marking/twintri.png
+  skins/marking/uppy.png
+  skins/marking/warpaint.png
+  skins/marking/whisker.png
+  skins/pinky.json
+  skins/redbopp.json
+  skins/redstripe.json
+  skins/saddo.json
+  skins/toptri.json
+  skins/twinbop.json
+  skins/twintri.json
+  skins/warpaint.json
+  skins/x_ninja.json
+  skins/xmas_hat.png
+  ui/blob.png
+  ui/console.png
+  ui/console_bar.png
+  ui/debug_font.png
+  ui/demo_buttons.png
+  ui/file_icons.png
+  ui/gametypes/ctf.png
+  ui/gametypes/dm.png
+  ui/gametypes/lms.png
+  ui/gametypes/lts.png
+  ui/gametypes/mod.png
+  ui/gametypes/tdm.png
+  ui/gui_buttons.png
+  ui/gui_cursor.png
+  ui/gui_icons.png
+  ui/gui_logo.png
+  ui/icons/arrows.png
+  ui/icons/browse.png
+  ui/icons/chat_whisper.png
+  ui/icons/friend.png
+  ui/icons/info.png
+  ui/icons/level.png
+  ui/icons/menu.png
+  ui/icons/sidebar.png
+  ui/icons/tools.png
+  ui/menuimages/demos.png
+  ui/menuimages/editor.png
+  ui/menuimages/local_server.png
+  ui/menuimages/play_game.png
+  ui/menuimages/settings.png
+  ui/no_skinpart.png
+  ui/themes/heavens.png
+  ui/themes/heavens_day.map
+  ui/themes/heavens_night.map
+  ui/themes/jungle.png
+  ui/themes/jungle_day.map
+  ui/themes/jungle_night.map
+  ui/themes/none.png
+)
+
+########################################################################
+# COPY DATA AND DLLS
+########################################################################
+
+foreach(datafile ${DATA})
+  file(RELATIVE_PATH OUT ${PROJECT_SOURCE_DIR}/datasrc ${datafile})
+  get_filename_component(DESTINATION data/${OUT} PATH)
+  file(MAKE_DIRECTORY ${DESTINATION})
+  file(COPY ${datafile} DESTINATION ${DESTINATION})
+endforeach()
+set(COPY_FILES
+  ${FREETYPE_COPY_FILES}
+  ${SDL2_COPY_FILES}
+)
+file(COPY ${COPY_FILES} DESTINATION .)
+
+########################################################################
+# CODE GENERATION
+########################################################################
+
+function(chash output_file)
+  add_custom_command(OUTPUT ${output_file}
+    COMMAND ${PYTHON_EXECUTABLE} scripts/cmd5.py ${ARGN}
+      > "${PROJECT_BINARY_DIR}/${output_file}"
+    DEPENDS scripts/cmd5.py ${ARGN}
+    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+  )
+endfunction()
+
+function(generate_source output_file script_parameter)
+  add_custom_command(OUTPUT ${output_file}
+    COMMAND ${PYTHON_EXECUTABLE} datasrc/compile.py ${script_parameter}
+      > "${PROJECT_BINARY_DIR}/${output_file}"
+    DEPENDS
+      datasrc/compile.py
+      datasrc/content.py
+      datasrc/datatypes.py
+      datasrc/network.py
+    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+  )
+endfunction()
+
+file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/src/generated/")
+if(GIT_FOUND)
+  execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --git-dir
+    ERROR_QUIET
+    OUTPUT_VARIABLE PROJECT_GIT_DIR
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    RESULT_VARIABLE PROJECT_GIT_DIR_ERROR
+  )
+  if(NOT PROJECT_GIT_DIR_ERROR)
+    set(GIT_REVISION_EXTRA_DEPS
+      ${PROJECT_GIT_DIR}/index
+      ${PROJECT_GIT_DIR}/logs/HEAD
+    )
+  endif()
+endif()
+add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/generated/git_revision.cpp
+  COMMAND ${PYTHON_EXECUTABLE}
+    scripts/git_revision.py
+    > ${PROJECT_BINARY_DIR}/src/generated/git_revision.cpp
+  WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+  DEPENDS
+    ${GIT_REVISION_EXTRA_DEPS}
+    scripts/git_revision.py
+)
+chash("src/generated/nethash.cpp"
+  "src/engine/shared/protocol.h"
+  "src/game/tuning.h"
+  "src/game/gamecore.cpp"
+  "${PROJECT_BINARY_DIR}/src/generated/protocol.h"
+)
+generate_source("src/generated/client_data.cpp" "client_content_source")
+generate_source("src/generated/client_data.h" "client_content_header")
+generate_source("src/generated/protocol.cpp" "network_source")
+generate_source("src/generated/protocol.h" "network_header")
+generate_source("src/generated/server_data.cpp" "server_content_source")
+generate_source("src/generated/server_data.h" "server_content_header")
+
+
+########################################################################
+# SHARED
+########################################################################
+
+# Sources
+set_src(BASE GLOB_RECURSE src/base
+  color.h
+  detect.h
+  math.h
+  system.c
+  system.h
+  tl/algorithm.h
+  tl/allocator.h
+  tl/array.h
+  tl/base.h
+  tl/range.h
+  tl/sorted_array.h
+  tl/string.h
+  tl/threading.h
+  vmath.h
+)
+set_src(ENGINE_INTERFACE GLOB src/engine
+  client.h
+  config.h
+  console.h
+  demo.h
+  editor.h
+  engine.h
+  friends.h
+  graphics.h
+  input.h
+  kernel.h
+  keys.h
+  map.h
+  masterserver.h
+  message.h
+  server.h
+  serverbrowser.h
+  sound.h
+  storage.h
+  textrender.h
+)
+set_src(ENGINE_SHARED GLOB src/engine/shared
+  compression.cpp
+  compression.h
+  config.cpp
+  config.h
+  config_variables.h
+  console.cpp
+  console.h
+  datafile.cpp
+  datafile.h
+  demo.cpp
+  demo.h
+  econ.cpp
+  econ.h
+  engine.cpp
+  filecollection.cpp
+  filecollection.h
+  huffman.cpp
+  huffman.h
+  jobs.cpp
+  jobs.h
+  kernel.cpp
+  linereader.cpp
+  linereader.h
+  map.cpp
+  mapchecker.cpp
+  mapchecker.h
+  masterserver.cpp
+  memheap.cpp
+  memheap.h
+  message.h
+  netban.cpp
+  netban.h
+  network.cpp
+  network.h
+  network_client.cpp
+  network_conn.cpp
+  network_console.cpp
+  network_console_conn.cpp
+  network_server.cpp
+  network_token.cpp
+  packer.cpp
+  packer.h
+  protocol.h
+  ringbuffer.cpp
+  ringbuffer.h
+  snapshot.cpp
+  snapshot.h
+  storage.cpp
+)
+set(ENGINE_GENERATED_SHARED src/generated/protocol.cpp src/generated/protocol.h)
+set_src(GAME_SHARED GLOB src/game
+  collision.cpp
+  collision.h
+  gamecore.cpp
+  gamecore.h
+  layers.cpp
+  layers.h
+  mapitems.h
+  tuning.h
+  variables.h
+  version.h
+  voting.h
+)
+set(GAME_GENERATED_SHARED
+  src/generated/git_revision.cpp
+  src/generated/nethash.cpp
+  src/generated/protocol.h
+)
+
+set(DEPS ${DEP_MD5} ${ZLIB_DEP})
+
+# Libraries
+set(LIBS ${CMAKE_THREAD_LIBS_INIT} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS})
+
+# Targets
+add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_GENERATED_SHARED} ${BASE})
+add_library(game-shared EXCLUDE_FROM_ALL OBJECT ${GAME_SHARED} ${GAME_GENERATED_SHARED})
+list(APPEND TARGETS_OWN engine-shared game-shared)
+
+
+########################################################################
+# CLIENT
+########################################################################
+
+if(CLIENT)
+  # Sources
+  set_src(ENGINE_CLIENT GLOB src/engine/client
+    backend_sdl.cpp
+    backend_sdl.h
+    client.cpp
+    client.h
+    friends.cpp
+    friends.h
+    graphics_threaded.cpp
+    graphics_threaded.h
+    input.cpp
+    input.h
+    keynames.h
+    serverbrowser.cpp
+    serverbrowser.h
+    serverbrowser_entry.h
+    serverbrowser_fav.cpp
+    serverbrowser_fav.h
+    serverbrowser_filter.cpp
+    serverbrowser_filter.h
+    sound.cpp
+    sound.h
+    text.cpp
+  )
+  set_src(GAME_CLIENT GLOB_RECURSE src/game/client
+    animstate.cpp
+    animstate.h
+    component.h
+    components/binds.cpp
+    components/binds.h
+    components/broadcast.cpp
+    components/broadcast.h
+    components/camera.cpp
+    components/camera.h
+    components/chat.cpp
+    components/chat.h
+    components/console.cpp
+    components/console.h
+    components/controls.cpp
+    components/controls.h
+    components/countryflags.cpp
+    components/countryflags.h
+    components/damageind.cpp
+    components/damageind.h
+    components/debughud.cpp
+    components/debughud.h
+    components/effects.cpp
+    components/effects.h
+    components/emoticon.cpp
+    components/emoticon.h
+    components/flow.cpp
+    components/flow.h
+    components/hud.cpp
+    components/hud.h
+    components/items.cpp
+    components/items.h
+    components/killmessages.cpp
+    components/killmessages.h
+    components/mapimages.cpp
+    components/mapimages.h
+    components/maplayers.cpp
+    components/maplayers.h
+    components/menus.cpp
+    components/menus.h
+    components/menus_browser.cpp
+    components/menus_callback.cpp
+    components/menus_demo.cpp
+    components/menus_ingame.cpp
+    components/menus_popups.cpp
+    components/menus_settings.cpp
+    components/menus_start.cpp
+    components/motd.cpp
+    components/motd.h
+    components/nameplates.cpp
+    components/nameplates.h
+    components/particles.cpp
+    components/particles.h
+    components/players.cpp
+    components/players.h
+    components/scoreboard.cpp
+    components/scoreboard.h
+    components/skins.cpp
+    components/skins.h
+    components/sounds.cpp
+    components/sounds.h
+    components/spectator.cpp
+    components/spectator.h
+    components/voting.cpp
+    components/voting.h
+    gameclient.cpp
+    gameclient.h
+    lineinput.cpp
+    lineinput.h
+    localization.cpp
+    localization.h
+    render.cpp
+    render.h
+    render_map.cpp
+    ui.cpp
+    ui.h
+  )
+  set_src(GAME_EDITOR GLOB src/game/editor
+    auto_map.cpp
+    auto_map.h
+    editor.cpp
+    editor.h
+    io.cpp
+    layer_game.cpp
+    layer_quads.cpp
+    layer_tiles.cpp
+    popups.cpp
+  )
+  set(GAME_GENERATED_CLIENT
+    src/generated/client_data.cpp
+    src/generated/client_data.h
+  )
+  set(CLIENT_SRC ${PLATFORM_CLIENT} ${ENGINE_CLIENT} ${GAME_CLIENT} ${GAME_EDITOR} ${GAME_GENERATED_CLIENT})
+
+  set(DEPS_CLIENT ${DEPS} ${PNGLITE_DEP} ${WAVPACK_DEP})
+
+  # Libraries
+  set(LIBS_CLIENT
+    ${LIBS}
+    ${FREETYPE_LIBRARIES}
+    ${PNGLITE_LIBRARIES}
+    ${SDL2_LIBRARIES}
+    ${WAVPACK_LIBRARIES}
+    ${PLATFORM_CLIENT_LIBS}
+  )
+
+  if(TARGET_OS STREQUAL "windows")
+    set(CLIENT_ICON "other/icons/${CLIENT_EXECUTABLE}.rc")
+  else()
+    set(CLIENT_ICON)
+  endif()
+
+  # Target
+  set(TARGET_CLIENT ${CLIENT_EXECUTABLE})
+  add_executable(${TARGET_CLIENT}
+    ${CLIENT_SRC}
+    ${CLIENT_ICON}
+    ${CLIENT_MANIFEST}
+    ${DEPS_CLIENT}
+    $<TARGET_OBJECTS:engine-shared>
+    $<TARGET_OBJECTS:game-shared>
+  )
+  target_link_libraries(${TARGET_CLIENT} ${LIBS_CLIENT})
+
+  target_include_directories(${TARGET_CLIENT} PRIVATE
+    ${FREETYPE_INCLUDE_DIRS}
+    ${PNGLITE_INCLUDE_DIRS}
+    ${SDL2_INCLUDE_DIRS}
+    ${WAVPACK_INCLUDE_DIRS}
+  )
+
+  set(PARAMS "${WAVPACK_INCLUDE_DIRS};${WAVPACK_INCLUDE_DIRS}")
+  if(NOT(WAVPACK_OPEN_FILE_INPUT_EX_PARAMS STREQUAL PARAMS))
+    unset(WAVPACK_OPEN_FILE_INPUT_EX CACHE)
+  endif()
+  set(WAVPACK_OPEN_FILE_INPUT_EX_PARAMS "${PARAMS}" CACHE INTERNAL "")
+
+  set(CMAKE_REQUIRED_INCLUDES ${ORIGINAL_CMAKE_REQUIRED_INCLUDES} ${WAVPACK_INCLUDE_DIRS})
+  set(CMAKE_REQUIRED_LIBRARIES ${ORIGINAL_CMAKE_REQUIRED_LIBRARIES} ${WAVPACK_LIBRARIES})
+  check_symbol_exists(WavpackOpenFileInputEx wavpack.h WAVPACK_OPEN_FILE_INPUT_EX)
+  set(CMAKE_REQUIRED_INCLUDES ${ORIGINAL_CMAKE_REQUIRED_INCLUDES})
+  set(CMAKE_REQUIRED_LIBRARIES ${ORIGINAL_CMAKE_REQUIRED_LIBRARIES})
+
+  if(WAVPACK_OPEN_FILE_INPUT_EX)
+    target_compile_definitions(${TARGET_CLIENT} PRIVATE CONF_WAVPACK_OPEN_FILE_INPUT_EX)
+  endif()
+
+  list(APPEND TARGETS_OWN ${TARGET_CLIENT})
+  list(APPEND TARGETS_LINK ${TARGET_CLIENT})
+endif()
+
+
+########################################################################
+# SERVER
+########################################################################
+
+# Sources
+set_src(ENGINE_SERVER GLOB src/engine/server
+  register.cpp
+  register.h
+  server.cpp
+  server.h
+)
+set_src(GAME_SERVER GLOB_RECURSE src/game/server
+  alloc.h
+  entities/character.cpp
+  entities/character.h
+  entities/flag.cpp
+  entities/flag.h
+  entities/laser.cpp
+  entities/laser.h
+  entities/pickup.cpp
+  entities/pickup.h
+  entities/projectile.cpp
+  entities/projectile.h
+  entity.cpp
+  entity.h
+  eventhandler.cpp
+  eventhandler.h
+  gamecontext.cpp
+  gamecontext.h
+  gamecontroller.cpp
+  gamecontroller.h
+  gamemodes/ctf.cpp
+  gamemodes/ctf.h
+  gamemodes/dm.cpp
+  gamemodes/dm.h
+  gamemodes/lms.cpp
+  gamemodes/lms.h
+  gamemodes/lts.cpp
+  gamemodes/lts.h
+  gamemodes/mod.cpp
+  gamemodes/mod.h
+  gamemodes/tdm.cpp
+  gamemodes/tdm.h
+  gameworld.cpp
+  gameworld.h
+  player.cpp
+  player.h
+)
+set(GAME_GENERATED_SERVER
+  src/generated/server_data.cpp
+  src/generated/server_data.h
+)
+set(SERVER_SRC ${ENGINE_SERVER} ${GAME_SERVER} ${GAME_GENERATED_SERVER})
+if(TARGET_OS STREQUAL "windows")
+  set(SERVER_ICON "other/icons/${SERVER_EXECUTABLE}.rc")
+else()
+  set(SERVER_ICON)
+endif()
+
+# Libraries
+set(LIBS_SERVER ${LIBS})
+
+# Target
+set(TARGET_SERVER ${SERVER_EXECUTABLE})
+add_executable(${TARGET_SERVER}
+  ${DEPS}
+  ${SERVER_SRC}
+  ${SERVER_ICON}
+  $<TARGET_OBJECTS:engine-shared>
+  $<TARGET_OBJECTS:game-shared>
+)
+target_link_libraries(${TARGET_SERVER} ${LIBS_SERVER})
+list(APPEND TARGETS_OWN ${TARGET_SERVER})
+list(APPEND TARGETS_LINK ${TARGET_SERVER})
+
+if(TARGET_OS AND TARGET_OS STREQUAL "mac")
+  set(SERVER_LAUNCHER_SRC src/osxlaunch/server.mm)
+  set(TARGET_SERVER_LAUNCHER ${TARGET_SERVER}-Launcher)
+  add_executable(${TARGET_SERVER_LAUNCHER} ${SERVER_LAUNCHER_SRC})
+  target_link_libraries(${TARGET_SERVER_LAUNCHER} ${COCOA})
+  list(APPEND TARGETS_OWN ${TARGET_SERVER_LAUNCHER})
+  list(APPEND TARGETS_LINK ${TARGET_SERVER_LAUNCHER})
+endif()
+
+########################################################################
+# VARIOUS TARGETS
+########################################################################
+
+set_src(MASTERSRV_SRC GLOB src/mastersrv mastersrv.cpp mastersrv.h)
+set_src(VERSIONSRV_SRC GLOB src/versionsrv mapversions.h versionsrv.cpp versionsrv.h)
+
+set(TARGET_MASTERSRV mastersrv)
+set(TARGET_VERSIONSRV versionsrv)
+
+add_executable(${TARGET_MASTERSRV} EXCLUDE_FROM_ALL ${MASTERSRV_SRC} $<TARGET_OBJECTS:engine-shared> ${DEPS})
+add_executable(${TARGET_VERSIONSRV} EXCLUDE_FROM_ALL ${VERSIONSRV_SRC} $<TARGET_OBJECTS:engine-shared> ${DEPS})
+
+target_link_libraries(${TARGET_MASTERSRV} ${LIBS})
+target_link_libraries(${TARGET_VERSIONSRV} ${LIBS})
+
+list(APPEND TARGETS_OWN ${TARGET_MASTERSRV} ${TARGET_VERSIONSRV})
+list(APPEND TARGETS_LINK ${TARGET_MASTERSRV} ${TARGET_VERSIONSRV})
+
+set(TARGETS_TOOLS)
+set_src(TOOLS GLOB src/tools
+  crapnet.cpp
+  fake_server.cpp
+  map_resave.cpp
+  map_version.cpp
+  packetgen.cpp
+)
+foreach(ABS_T ${TOOLS})
+  file(RELATIVE_PATH T "${PROJECT_SOURCE_DIR}/src/tools/" ${ABS_T})
+  if(T MATCHES "\\.cpp$")
+    string(REGEX REPLACE "\\.cpp$" "" TOOL "${T}")
+    add_executable(${TOOL} EXCLUDE_FROM_ALL
+      ${DEPS}
+      src/tools/${TOOL}.cpp
+      ${EXTRA_TOOL_SRC}
+      $<TARGET_OBJECTS:engine-shared>
+    )
+    target_link_libraries(${TOOL} ${LIBS})
+    list(APPEND TARGETS_TOOLS ${TOOL})
+  endif()
+endforeach()
+
+list(APPEND TARGETS_OWN ${TARGETS_TOOLS})
+list(APPEND TARGETS_LINK ${TARGETS_TOOLS})
+
+add_custom_target(tools DEPENDS ${TARGETS_TOOLS})
+add_custom_target(everything DEPENDS ${TARGETS_OWN})
+
+########################################################################
+# INSTALLATION
+########################################################################
+
+function(escape_regex VAR STRING)
+  string(REGEX REPLACE "([][^$.+*?|()\\\\])" "\\\\\\1" ESCAPED "${STRING}")
+  set(${VAR} ${ESCAPED} PARENT_SCOPE)
+endfunction()
+
+function(escape_backslashes VAR STRING)
+  string(REGEX REPLACE "\\\\" "\\\\\\\\" ESCAPED "${STRING}")
+  set(${VAR} ${ESCAPED} PARENT_SCOPE)
+endfunction()
+
+function(max_length VAR)
+  set(MAX_LENGTH 0)
+  foreach(str ${ARGN})
+    string(LENGTH ${str} LENGTH)
+    if(LENGTH GREATER MAX_LENGTH)
+      set(MAX_LENGTH ${LENGTH})
+    endif()
+  endforeach()
+  set(${VAR} ${MAX_LENGTH} PARENT_SCOPE)
+endfunction()
+
+# Tries to generate a list of regex that matches everything except the given
+# parameters.
+function(regex_inverted VAR)
+  max_length(MAX_LENGTH ${ARGN})
+  math(EXPR UPPER_BOUND "${MAX_LENGTH}-1")
+
+  set(REMAINING ${ARGN})
+  set(RESULT)
+
+  foreach(i RANGE ${UPPER_BOUND})
+    set(TEMP ${REMAINING})
+    set(REMAINING)
+    foreach(str ${TEMP})
+      string(LENGTH ${str} LENGTH)
+      if(i LESS LENGTH)
+        list(APPEND REMAINING ${str})
+      endif()
+    endforeach()
+
+    set(ADDITIONAL)
+    foreach(outer ${REMAINING})
+      string(SUBSTRING ${outer} 0 ${i} OUTER_PREFIX)
+      set(CHARS "")
+      foreach(inner ${REMAINING})
+        string(SUBSTRING ${inner} 0 ${i} INNER_PREFIX)
+        if(OUTER_PREFIX STREQUAL INNER_PREFIX)
+          string(SUBSTRING ${inner} ${i} 1 INNER_NEXT)
+          set(CHARS "${CHARS}${INNER_NEXT}")
+        endif()
+      endforeach()
+      escape_regex(OUTER_PREFIX_ESCAPED "${OUTER_PREFIX}")
+
+      list(APPEND ADDITIONAL "${OUTER_PREFIX_ESCAPED}([^${CHARS}]|$)")
+    endforeach()
+    list(REMOVE_DUPLICATES ADDITIONAL)
+    list(APPEND RESULT ${ADDITIONAL})
+  endforeach()
+  set(${VAR} ${RESULT} PARENT_SCOPE)
+endfunction()
+
+set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
+set(CPACK_GENERATOR TGZ TXZ)
+set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
+set(CPACK_STRIP_FILES TRUE)
+set(CPACK_COMPONENTS_ALL portable)
+set(CPACK_SOURCE_GENERATOR ZIP TGZ TBZ2 TXZ)
+set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
+set(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH})
+set(CPACK_SYSTEM_NAME ${CMAKE_SYSTEM_NAME})
+
+if(TARGET_OS AND TARGET_BITS)
+  if(TARGET_OS STREQUAL "windows")
+    set(CPACK_SYSTEM_NAME "win${TARGET_BITS}")
+    set(CPACK_GENERATOR ZIP)
+  elseif(TARGET_OS STREQUAL "linux")
+    # Assuming Intel here.
+    if(TARGET_BITS EQUAL 32)
+      set(CPACK_SYSTEM_NAME "linux_x86")
+    elseif(TARGET_BITS EQUAL 64)
+      set(CPACK_SYSTEM_NAME "linux_x86_64")
+    endif()
+  elseif(TARGET_OS STREQUAL "mac")
+    set(CPACK_SYSTEM_NAME "osx")
+    set(CPACK_GENERATOR DMG)
+  endif()
+endif()
+
+set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME})
+set(CPACK_ARCHIVE_PORTABLE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME})
+set(CPACK_SOURCE_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-src)
+set(CPACK_SOURCE_FILES
+  CMakeLists.txt
+  bam.lua
+  cmake/
+  configure.lua
+  datasrc/
+  license.txt
+  other/
+  readme.md
+  scripts/
+  src/
+  storage.cfg
+)
+set(CPACK_SOURCE_IGNORE_FILES
+  "\\\\.o$"
+  "\\\\.pyc$"
+  "/\\\\.git"
+  "/__pycache__/"
+)
+
+regex_inverted(CPACK_SOURCE_FILES_INVERTED ${CPACK_SOURCE_FILES})
+escape_regex(PROJECT_SOURCE_DIR_ESCAPED ${PROJECT_SOURCE_DIR})
+
+foreach(str ${CPACK_SOURCE_FILES_INVERTED})
+  escape_backslashes(STR_ESCAPED "${PROJECT_SOURCE_DIR_ESCAPED}/${str}")
+  list(APPEND CPACK_SOURCE_IGNORE_FILES "${STR_ESCAPED}")
+endforeach()
+
+set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME ${PROJECT_NAME})
+
+set(CPACK_TARGETS
+  ${TARGET_CLIENT}
+  ${TARGET_SERVER}
+)
+set(CPACK_DIRS ${PROJECT_BINARY_DIR}/data)
+set(CPACK_FILES
+  license.txt
+  storage.cfg
+  ${COPY_FILES}
+)
+if(TARGET_OS STREQUAL "windows")
+  list(APPEND CPACK_FILES other/config_directory.bat)
+endif()
+
+if(NOT DEV)
+  install(DIRECTORY ${PROJECT_BINARY_DIR}/data DESTINATION share/${PROJECT_NAME} COMPONENT data)
+  install(TARGETS ${TARGET_CLIENT} DESTINATION bin COMPONENT client)
+  install(TARGETS ${TARGET_SERVER} DESTINATION bin COMPONENT server)
+endif()
+
+if(DEV)
+  # Don't generate CPack targets.
+elseif(CMAKE_VERSION VERSION_LESS 3.6 OR CMAKE_VERSION VERSION_EQUAL 3.6)
+  message(WARNING "Cannot create CPack targets, CMake version too old. Use CMake 3.6 or newer.")
+else()
+  set(EXTRA_ARGS DESTINATION ${CPACK_PACKAGE_FILE_NAME} COMPONENT portable EXCLUDE_FROM_ALL)
+  install(TARGETS ${CPACK_TARGETS} ${EXTRA_ARGS})
+  install(DIRECTORY ${CPACK_DIRS} ${EXTRA_ARGS})
+  install(FILES ${CPACK_FILES} ${EXTRA_ARGS})
+endif()
+
+set(PACKAGE_TARGETS)
+
+if(CLIENT AND (DMGTOOLS_FOUND OR HDIUTIL))
+  file(MAKE_DIRECTORY bundle/client/)
+  file(MAKE_DIRECTORY bundle/server/)
+  configure_file(other/bundle/client/Info.plist.in bundle/client/Info.plist)
+  configure_file(other/bundle/server/Info.plist.in bundle/server/Info.plist)
+
+  if(HDIUTIL)
+    set(DMG_PARAMS --hdiutil ${HDIUTIL})
+  elseif(DMGTOOLS_FOUND)
+    set(DMG_PARAMS --dmgtools ${DMG} ${HFSPLUS} ${NEWFS_HFS})
+  endif()
+  set(DMG_TMPDIR pack_${CPACK_PACKAGE_FILE_NAME}_dmg)
+  set(DMG_MKDIRS
+    ${TARGET_CLIENT}.app
+    ${TARGET_CLIENT}.app/Contents
+    ${TARGET_CLIENT}.app/Contents/Frameworks
+    ${TARGET_CLIENT}.app/Contents/MacOS
+    ${TARGET_CLIENT}.app/Contents/Resources
+    ${TARGET_SERVER}.app
+    ${TARGET_SERVER}.app/Contents
+    ${TARGET_SERVER}.app/Contents/MacOS
+    ${TARGET_SERVER}.app/Contents/Resources
+    ${TARGET_SERVER}.app/Contents/Resources/data
+    # Needed so the server recognizes the data directory.
+    ${TARGET_SERVER}.app/Contents/Resources/data/mapres
+  )
+  set(DMG_MKDIR_COMMANDS)
+  foreach(dir ${DMG_MKDIRS})
+    list(APPEND DMG_MKDIR_COMMANDS COMMAND ${CMAKE_COMMAND} -E make_directory ${DMG_TMPDIR}/${dir})
+  endforeach()
+  add_custom_command(OUTPUT ${CPACK_PACKAGE_FILE_NAME}.dmg
+    COMMAND ${CMAKE_COMMAND} -E remove_directory ${DMG_TMPDIR}
+    ${DMG_MKDIR_COMMANDS}
+
+    # CLIENT
+    COMMAND ${CMAKE_COMMAND} -E copy_directory data ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/Resources/data
+    COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/other/icons/${TARGET_CLIENT}.icns ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/Resources/
+    COMMAND ${CMAKE_COMMAND} -E copy bundle/client/Info.plist ${PROJECT_SOURCE_DIR}/other/bundle/client/PkgInfo ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/
+    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${TARGET_CLIENT}> ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/MacOS/
+    COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/other/sdl/mac/lib64/SDL2.framework ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/Frameworks/SDL2.framework
+    COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/other/freetype/mac/lib64/libfreetype.6.dylib ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/Frameworks/
+    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/darwin_change_dylib.py change --tools ${CMAKE_INSTALL_NAME_TOOL} ${CMAKE_OTOOL} ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/MacOS/${TARGET_CLIENT} SDL2 @executable_path/../Frameworks/SDL2.framework/SDL2
+    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/darwin_change_dylib.py change --tools ${CMAKE_INSTALL_NAME_TOOL} ${CMAKE_OTOOL} ${DMG_TMPDIR}/${TARGET_CLIENT}.app/Contents/MacOS/${TARGET_CLIENT} libfreetype @executable_path/../Frameworks/libfreetype.6.dylib
+
+    # SERVER
+    COMMAND ${CMAKE_COMMAND} -E copy_directory data/maps ${DMG_TMPDIR}/${TARGET_SERVER}.app/Contents/Resources/data/maps
+    COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/other/icons/${TARGET_SERVER}.icns ${DMG_TMPDIR}/${TARGET_SERVER}.app/Contents/Resources/
+    COMMAND ${CMAKE_COMMAND} -E copy bundle/server/Info.plist ${PROJECT_SOURCE_DIR}/other/bundle/server/PkgInfo ${DMG_TMPDIR}/${TARGET_SERVER}.app/Contents/
+    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${TARGET_SERVER}> $<TARGET_FILE:${TARGET_SERVER_LAUNCHER}> ${DMG_TMPDIR}/${TARGET_SERVER}.app/Contents/MacOS/
+
+    # DMG
+    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/dmg.py create ${DMG_PARAMS} ${CPACK_PACKAGE_FILE_NAME}.dmg ${CPACK_PACKAGE_FILE_NAME} ${DMG_TMPDIR}
+
+    DEPENDS
+      ${TARGET_CLIENT}
+      ${TARGET_SERVER_LAUNCHER}
+      ${TARGET_SERVER}
+      ${PROJECT_BINARY_DIR}/bundle/client/Info.plist
+      ${PROJECT_BINARY_DIR}/bundle/server/Info.plist
+      other/bundle/client/PkgInfo
+      other/bundle/server/PkgInfo
+      other/icons/${TARGET_CLIENT}.icns
+      other/icons/${TARGET_SERVER}.icns
+      scripts/dmg.py
+  )
+  add_custom_target(package_dmg DEPENDS ${CPACK_PACKAGE_FILE_NAME}.dmg)
+  list(APPEND PACKAGE_TARGETS package_dmg)
+endif()
+
+foreach(ext zip tar.gz tar.xz)
+  set(TAR_MODE c)
+  set(TAR_EXTRA_ARGS)
+  string(REPLACE . _ EXT_SLUG ${ext})
+
+  set(TMPDIR pack_${CPACK_PACKAGE_FILE_NAME}_${EXT_SLUG}/${CPACK_PACKAGE_FILE_NAME})
+
+  set(COPY_FILE_COMMANDS)
+  set(COPY_DIR_COMMANDS)
+  set(COPY_TARGET_COMMANDS)
+  set(STRIP_TARGET_COMMANDS)
+  foreach(file ${CPACK_FILES})
+    list(APPEND COPY_FILE_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/${file} ${TMPDIR}/)
+  endforeach()
+  foreach(dir ${CPACK_DIRS})
+    get_filename_component(NAME ${dir} NAME)
+    list(APPEND COPY_DIR_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy_directory ${dir} ${TMPDIR}/${NAME})
+  endforeach()
+  foreach(target ${CPACK_TARGETS})
+    list(APPEND COPY_TARGET_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${target}> ${TMPDIR}/)
+  endforeach()
+
+  if(ext STREQUAL zip)
+    set(TAR_EXTRA_ARGS --format=zip)
+  elseif(ext STREQUAL tar.gz)
+    set(TAR_MODE cz)
+  elseif(ext STREQUAL tar.xz)
+    set(TAR_MODE cJ)
+  endif()
+  add_custom_command(OUTPUT ${CPACK_PACKAGE_FILE_NAME}.${ext}
+    COMMAND ${CMAKE_COMMAND} -E remove_directory ${TMPDIR}
+    COMMAND ${CMAKE_COMMAND} -E make_directory ${TMPDIR}
+    ${COPY_FILE_COMMANDS}
+    ${COPY_DIR_COMMANDS}
+    ${COPY_TARGET_COMMANDS}
+    ${STRIP_TARGET_COMMANDS}
+    COMMAND ${CMAKE_COMMAND} -E chdir pack_${CPACK_PACKAGE_FILE_NAME}_${EXT_SLUG} ${CMAKE_COMMAND} -E tar ${TAR_MODE} ../${CPACK_PACKAGE_FILE_NAME}.${ext} ${TAR_EXTRA_ARGS} -- ${CPACK_PACKAGE_FILE_NAME}/
+    DEPENDS ${CPACK_TARGETS}
+  )
+  add_custom_target(package_${EXT_SLUG} DEPENDS ${CPACK_PACKAGE_FILE_NAME}.${ext})
+  list(APPEND PACKAGE_TARGETS package_${EXT_SLUG})
+endforeach()
+
+set(PACKAGE_DEFAULT tar_xz)
+if(TARGET_OS STREQUAL "windows")
+  set(PACKAGE_DEFAULT zip)
+elseif(TARGET_OS STREQUAL "mac")
+  set(PACKAGE_DEFAULT dmg)
+endif()
+add_custom_target(package_default DEPENDS package_${PACKAGE_DEFAULT})
+
+add_custom_target(package_all DEPENDS ${PACKAGE_TARGETS})
+
+# Unset these variables, they might do something in the future of CPack.
+unset(CPACK_SOURCE_FILES)
+unset(CPACK_SOURCE_FILES_INVERTED)
+unset(CPACK_TARGETS)
+unset(CPACK_DIRS)
+unset(CPACK_FILES)
+
+include(CPack)
+
+########################################################################
+# COMPILER-SPECIFICS
+########################################################################
+
+# In the future (CMake 3.8.0+), use source_group(TREE ...)
+macro(source_group_tree dir)
+  file(GLOB ents RELATIVE ${PROJECT_SOURCE_DIR}/${dir} ${PROJECT_SOURCE_DIR}/${dir}/*)
+  foreach(ent ${ents})
+    if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${dir}/${ent})
+      source_group_tree(${dir}/${ent})
+    else()
+      string(REPLACE "/" "\\" group ${dir})
+      source_group(${group} FILES ${PROJECT_SOURCE_DIR}/${dir}/${ent})
+    endif()
+  endforeach()
+endmacro()
+source_group_tree(src)
+
+set(TARGETS ${TARGETS_OWN} ${TARGETS_DEP})
+
+foreach(target ${TARGETS})
+  if(MSVC)
+    target_compile_options(${target} PRIVATE $<$<NOT:${DBG}>:/MT> $<${DBG}:/MTd>) # Use static CRT
+    target_compile_options(${target} PRIVATE /MP) # Use multiple cores
+    target_compile_options(${target} PRIVATE /EHsc) # Only catch C++ exceptions with catch.
+    target_compile_options(${target} PRIVATE /GS) # Protect the stack pointer.
+    target_compile_options(${target} PRIVATE /wd4996) # Use of non-_s functions.
+  endif()
+  if(OUR_FLAGS)
+    target_compile_options(${target} PRIVATE ${OUR_FLAGS})
+  endif()
+  if(DEFINE_FORTIFY_SOURCE)
+    target_compile_definitions(${target} PRIVATE $<$<NOT:$<CONFIG:Debug>>:_FORTIFY_SOURCE=2>) # Detect some buffer overflows.
+  endif()
+endforeach()
+
+foreach(target ${TARGETS_LINK})
+  if(MSVC)
+    set_property(TARGET ${target} APPEND PROPERTY LINK_FLAGS /SAFESEH:NO) # Disable SafeSEH because the shipped libraries don't support it (would cause error LNK2026 otherwise).
+  endif()
+  if(TARGET_OS STREQUAL "mac")
+    target_link_libraries(${target} -stdlib=libc++)
+    target_link_libraries(${target} -mmacosx-version-min=10.7)
+  endif()
+  if((MINGW OR TARGET_OS STREQUAL "linux") AND PREFER_BUNDLED_LIBS)
+    # Statically link the standard libraries with on MinGW/Linux so we don't
+    # have to ship them as DLLs.
+    target_link_libraries(${target} -static-libgcc)
+    target_link_libraries(${target} -static-libstdc++)
+    if(MINGW)
+      # Link pthread library statically instead of dynamically.
+      # Solution from https://stackoverflow.com/a/28001261.
+      target_link_libraries(${target} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic)
+    endif()
+  endif()
+endforeach()
+
+foreach(target ${TARGETS_OWN})
+  if(MSVC)
+    target_compile_options(${target} PRIVATE /wd4244) # Possible loss of data (float -> int, int -> float, etc.).
+    target_compile_options(${target} PRIVATE /wd4267) # Possible loss of data (size_t - int on win64).
+    target_compile_options(${target} PRIVATE /wd4800) # Implicit conversion of int to bool.
+  endif()
+  if(OUR_FLAGS_OWN)
+    target_compile_options(${target} PRIVATE ${OUR_FLAGS_OWN})
+  endif()
+  target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src)
+  target_include_directories(${target} PRIVATE src)
+  target_compile_definitions(${target} PRIVATE $<$<CONFIG:Debug>:CONF_DEBUG>)
+  target_include_directories(${target} PRIVATE ${CURL_INCLUDE_DIRS})
+  target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS})
+endforeach()
+
+foreach(target ${TARGETS_DEP})
+  if(MSVC)
+    target_compile_options(${target} PRIVATE /W0)
+  endif()
+  if(OUR_FLAGS_DEP)
+    target_compile_options(${target} PRIVATE ${OUR_FLAGS_DEP})
+  endif()
+endforeach()
diff --git a/bam.lua b/bam.lua
index 77a1aab72..1988d217c 100644
--- a/bam.lua
+++ b/bam.lua
@@ -414,6 +414,8 @@ function GenerateSettings(conf, arch, builddir, compiler)
 	end
 	
 	settings.cc.includes:Add("src")
+	settings.cc.includes:Add("src/engine/external/pnglite")
+	settings.cc.includes:Add("src/engine/external/wavpack")
 	settings.cc.includes:Add(generated_src_dir)
 	
 	if family == "windows" then
diff --git a/cmake/FindFreetype.cmake b/cmake/FindFreetype.cmake
new file mode 100644
index 000000000..347b68442
--- /dev/null
+++ b/cmake/FindFreetype.cmake
@@ -0,0 +1,37 @@
+if(NOT CMAKE_CROSSCOMPILING)
+  find_package(PkgConfig QUIET)
+  pkg_check_modules(PC_FREETYPE freetype2)
+endif()
+
+set_extra_dirs_lib(FREETYPE freetype)
+find_library(FREETYPE_LIBRARY
+  NAMES freetype freetype.6
+  HINTS ${HINTS_FREETYPE_LIBDIR} ${PC_FREETYPE_LIBDIR} ${PC_FREETYPE_LIBRARY_DIRS}
+  PATHS ${PATHS_FREETYPE_LIBDIR}
+  ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+)
+set_extra_dirs_include(FREETYPE freetype "${FREETYPE_LIBRARY}")
+find_path(FREETYPE_INCLUDEDIR
+  NAMES config/ftheader.h freetype/config/ftheader.h
+  PATH_SUFFIXES freetype2
+  HINTS ${HINTS_FREETYPE_INCLUDEDIR} ${PC_FREETYPE_INCLUDEDIR} ${PC_FREETYPE_INCLUDE_DIRS}
+  PATHS ${PATHS_FREETYPE_INCLUDEDIR}
+  ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Freetype DEFAULT_MSG FREETYPE_LIBRARY FREETYPE_INCLUDEDIR)
+
+mark_as_advanced(FREETYPE_LIBRARY FREETYPE_INCLUDEDIR)
+
+if(FREETYPE_FOUND)
+  set(FREETYPE_LIBRARIES ${FREETYPE_LIBRARY})
+  set(FREETYPE_INCLUDE_DIRS ${FREETYPE_INCLUDEDIR})
+
+  is_bundled(FREETYPE_BUNDLED "${FREETYPE_LIBRARY}")
+  if(FREETYPE_BUNDLED AND TARGET_OS STREQUAL "windows")
+    set(FREETYPE_COPY_FILES "${EXTRA_FREETYPE_LIBDIR}/freetype.dll")
+  else()
+    set(FREETYPE_COPY_FILES)
+  endif()
+endif()
diff --git a/cmake/FindPnglite.cmake b/cmake/FindPnglite.cmake
new file mode 100644
index 000000000..6a877c257
--- /dev/null
+++ b/cmake/FindPnglite.cmake
@@ -0,0 +1,46 @@
+if(NOT PREFER_BUNDLED_LIBS)
+  if(NOT CMAKE_CROSSCOMPILING)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(PC_PNGLITE pnglite)
+  endif()
+
+  find_library(PNGLITE_LIBRARY
+    NAMES pnglite
+    HINTS ${PC_PNGLITE_LIBDIR} ${PC_PNGLITE_LIBRARY_DIRS}
+    ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+  )
+  find_path(PNGLITE_INCLUDEDIR
+    NAMES pnglite.h
+    HINTS ${PC_PNGLITE_INCLUDEDIR} ${PC_PNGLITE_INCLUDE_DIRS}
+    ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+  )
+
+  mark_as_advanced(PNGLITE_LIBRARY PNGLITE_INCLUDEDIR)
+
+  if(PNGLITE_LIBRARY AND PNGLITE_INCLUDEDIR)
+    include(FindPackageHandleStandardArgs)
+    find_package_handle_standard_args(Pnglite DEFAULT_MSG PNGLITE_LIBRARY PNGLITE_INCLUDEDIR)
+
+    set(PNGLITE_LIBRARIES ${PNGLITE_LIBRARY})
+    set(PNGLITE_INCLUDE_DIRS ${PNGLITE_INCLUDEDIR})
+    set(PNGLITE_BUNDLED OFF)
+  endif()
+endif()
+
+if(NOT PNGLITE_FOUND)
+  set(PNGLITE_SRC_DIR src/engine/external/pnglite)
+  set_src(PNGLITE_SRC GLOB ${PNGLITE_SRC_DIR} pnglite.c pnglite.h)
+  add_library(pnglite EXCLUDE_FROM_ALL OBJECT ${PNGLITE_SRC})
+  list(APPEND TARGETS_DEP pnglite)
+
+  set(PNGLITE_INCLUDEDIR ${PNGLITE_SRC_DIR})
+  target_include_directories(pnglite PRIVATE ${ZLIB_INCLUDE_DIRS})
+
+  set(PNGLITE_DEP $<TARGET_OBJECTS:pnglite>)
+  set(PNGLITE_INCLUDE_DIRS ${PNGLITE_INCLUDEDIR})
+  set(PNGLITE_LIBRARIES)
+
+  include(FindPackageHandleStandardArgs)
+  find_package_handle_standard_args(Pnglite DEFAULT_MSG PNGLITE_INCLUDEDIR)
+  set(PNGLITE_BUNDLED ON)
+endif()
diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake
new file mode 100644
index 000000000..94e293294
--- /dev/null
+++ b/cmake/FindSDL2.cmake
@@ -0,0 +1,48 @@
+if(NOT PREFER_BUNDLED_LIBS)
+  set(CMAKE_MODULE_PATH ${ORIGINAL_CMAKE_MODULE_PATH})
+  find_package(SDL2)
+  set(CMAKE_MODULE_PATH ${OWN_CMAKE_MODULE_PATH})
+  if(SDL2_FOUND)
+    set(SDL2_BUNDLED OFF)
+    set(SDL2_DEP)
+  endif()
+endif()
+
+if(NOT CMAKE_CROSSCOMPILING)
+  find_package(PkgConfig QUIET)
+  pkg_check_modules(PC_SDL2 sdl2)
+endif()
+
+set_extra_dirs_lib(SDL2 sdl)
+find_library(SDL2_LIBRARY
+  NAMES SDL2
+  HINTS ${HINTS_SDL2_LIBDIR} ${PC_SDL2_LIBDIR} ${PC_SDL2_LIBRARY_DIRS}
+  PATHS ${PATHS_SDL2_LIBDIR}
+  ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+)
+set(CMAKE_FIND_FRAMEWORK FIRST)
+set_extra_dirs_include(SDL2 sdl "${SDL2_LIBRARY}")
+# Looking for 'SDL.h' directly might accidentally find a SDL 1 instead of SDL 2
+# installation. Look for a header file only present in SDL 2 instead.
+find_path(SDL2_INCLUDEDIR SDL_assert.h
+  PATH_SUFFIXES SDL
+  HINTS ${HINTS_SDL2_INCLUDEDIR} ${PC_SDL2_INCLUDEDIR} ${PC_SDL2_INCLUDE_DIRS}
+  PATHS ${PATHS_SDL2_INCLUDEDIR}
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SDL2 DEFAULT_MSG SDL2_LIBRARY SDL2_INCLUDEDIR)
+
+mark_as_advanced(SDL2_LIBRARY SDL2_INCLUDEDIR)
+
+if(SDL2_FOUND)
+  set(SDL2_LIBRARIES ${SDL2_LIBRARY})
+  set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDEDIR})
+
+  is_bundled(SDL2_BUNDLED "${SDL2_LIBRARY}")
+  if(SDL2_BUNDLED AND TARGET_OS STREQUAL "windows")
+    set(SDL2_COPY_FILES "${EXTRA_SDL2_LIBDIR}/SDL2.dll")
+  else()
+    set(SDL2_COPY_FILES)
+  endif()
+endif()
diff --git a/cmake/FindWavpack.cmake b/cmake/FindWavpack.cmake
new file mode 100644
index 000000000..b7e855268
--- /dev/null
+++ b/cmake/FindWavpack.cmake
@@ -0,0 +1,52 @@
+if(NOT PREFER_BUNDLED_LIBS)
+  if(NOT CMAKE_CROSSCOMPILING)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(PC_WAVPACK wavpack)
+  endif()
+
+  find_library(WAVPACK_LIBRARY
+    NAMES wavpack
+    HINTS ${PC_WAVPACK_LIBDIR} ${PC_WAVPACK_LIBRARY_DIRS}
+    ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+  )
+  find_path(WAVPACK_INCLUDEDIR
+    NAMES wavpack.h
+    PATH_SUFFIXES wavpack
+    HINTS ${PC_WAVPACK_INCLUDEDIR} ${PC_WAVPACK_INCLUDE_DIRS}
+    ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
+  )
+
+  mark_as_advanced(WAVPACK_LIBRARY WAVPACK_INCLUDEDIR)
+
+  if(WAVPACK_LIBRARY AND WAVPACK_INCLUDEDIR)
+    include(FindPackageHandleStandardArgs)
+    find_package_handle_standard_args(Wavpack DEFAULT_MSG WAVPACK_LIBRARY WAVPACK_INCLUDEDIR)
+
+    set(WAVPACK_LIBRARIES ${WAVPACK_LIBRARY})
+    set(WAVPACK_INCLUDE_DIRS ${WAVPACK_INCLUDEDIR})
+    set(WAVPACK_BUNDLED OFF)
+  endif()
+endif()
+
+if(NOT WAVPACK_FOUND)
+  set(WAVPACK_SRC_DIR src/engine/external/wavpack)
+  set_src(WAVPACK_SRC GLOB ${WAVPACK_SRC_DIR}
+    bits.c
+    float.c
+    metadata.c
+    unpack.c
+    wavpack.h
+    words.c
+    wputils.c
+  )
+  add_library(wavpack EXCLUDE_FROM_ALL OBJECT ${WAVPACK_SRC})
+  set(WAVPACK_DEP $<TARGET_OBJECTS:wavpack>)
+  set(WAVPACK_INCLUDEDIR ${WAVPACK_SRC_DIR})
+  set(WAVPACK_INCLUDE_DIRS ${WAVPACK_INCLUDEDIR})
+
+  list(APPEND TARGETS_DEP wavpack)
+
+  include(FindPackageHandleStandardArgs)
+  find_package_handle_standard_args(Wavpack DEFAULT_MSG WAVPACK_INCLUDEDIR)
+  set(WAVPACK_BUNDLED ON)
+endif()
diff --git a/cmake/FindZLIB.cmake b/cmake/FindZLIB.cmake
new file mode 100644
index 000000000..a8dfca4d3
--- /dev/null
+++ b/cmake/FindZLIB.cmake
@@ -0,0 +1,50 @@
+if(NOT PREFER_BUNDLED_LIBS)
+  set(CMAKE_MODULE_PATH ${ORIGINAL_CMAKE_MODULE_PATH})
+  find_package(ZLIB)
+  set(CMAKE_MODULE_PATH ${OWN_CMAKE_MODULE_PATH})
+  if(ZLIB_FOUND)
+    set(ZLIB_BUNDLED OFF)
+    set(ZLIB_DEP)
+  endif()
+endif()
+
+if(NOT ZLIB_FOUND)
+  set(ZLIB_BUNDLED ON)
+  set(ZLIB_SRC_DIR src/engine/external/zlib)
+  set_src(ZLIB_SRC GLOB ${ZLIB_SRC_DIR}
+    adler32.c
+    compress.c
+    crc32.c
+    crc32.h
+    deflate.c
+    deflate.h
+    gzguts.h
+    infback.c
+    inffast.c
+    inffast.h
+    inffixed.h
+    inflate.c
+    inflate.h
+    inftrees.c
+    inftrees.h
+    trees.c
+    trees.h
+    uncompr.c
+    zconf.h
+    zlib.h
+    zutil.c
+    zutil.h
+  )
+  add_library(zlib EXCLUDE_FROM_ALL OBJECT ${ZLIB_SRC})
+  set(ZLIB_INCLUDEDIR ${ZLIB_SRC_DIR})
+  target_include_directories(zlib PRIVATE ${ZLIB_INCLUDEDIR})
+
+  set(ZLIB_DEP $<TARGET_OBJECTS:zlib>)
+  set(ZLIB_INCLUDE_DIRS ${ZLIB_INCLUDEDIR})
+  set(ZLIB_LIBRARIES)
+
+  list(APPEND TARGETS_DEP zlib)
+
+  include(FindPackageHandleStandardArgs)
+  find_package_handle_standard_args(ZLIB DEFAULT_MSG ZLIB_INCLUDEDIR)
+endif()
diff --git a/cmake/toolchains/darwin.toolchain b/cmake/toolchains/darwin.toolchain
new file mode 100644
index 000000000..6d1d6eb3e
--- /dev/null
+++ b/cmake/toolchains/darwin.toolchain
@@ -0,0 +1,11 @@
+set(CMAKE_SYSTEM_NAME Darwin)
+
+set(CMAKE_C_COMPILER o64-clang)
+set(CMAKE_CXX_COMPILER o64-clang++)
+set(CMAKE_INSTALL_NAME_TOOL x86_64-apple-darwin15-install_name_tool)
+set(CMAKE_OTOOL x86_64-apple-darwin15-otool)
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
diff --git a/cmake/toolchains/mingw32.toolchain b/cmake/toolchains/mingw32.toolchain
new file mode 100644
index 000000000..c4c065b7e
--- /dev/null
+++ b/cmake/toolchains/mingw32.toolchain
@@ -0,0 +1,10 @@
+set(CMAKE_SYSTEM_NAME Windows)
+
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
diff --git a/cmake/toolchains/mingw64.toolchain b/cmake/toolchains/mingw64.toolchain
new file mode 100644
index 000000000..bec41e944
--- /dev/null
+++ b/cmake/toolchains/mingw64.toolchain
@@ -0,0 +1,10 @@
+set(CMAKE_SYSTEM_NAME Windows)
+
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
diff --git a/other/bundle/client/Info.plist.in b/other/bundle/client/Info.plist.in
new file mode 100644
index 000000000..34d919702
--- /dev/null
+++ b/other/bundle/client/Info.plist.in
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+	<dict>
+		<key>CFBundleDevelopmentRegion</key>
+		<string>English</string>
+		<key>CFBundleExecutable</key>
+		<string>${TARGET_CLIENT}</string>
+		<key>CFBundleIconFile</key>
+		<string>${TARGET_CLIENT}</string>
+		<key>CFBundleInfoDictionaryVersion</key>
+		<string>6.0</string>
+		<key>CFBundlePackageType</key>
+		<string>APPL</string>
+		<key>CFBundleSignature</key>
+		<string>????</string>
+		<key>CFBundleVersion</key>
+		<string>${PROJECT_VERSION}</string>
+		<key>CFBundleIdentifier</key>
+		<string>com.teeworlds.app</string>
+		<key>NSHighResolutionCapable</key>
+		<true/>
+	</dict>
+</plist>
diff --git a/other/bundle/client/PkgInfo b/other/bundle/client/PkgInfo
new file mode 100644
index 000000000..6f749b0f3
--- /dev/null
+++ b/other/bundle/client/PkgInfo
@@ -0,0 +1 @@
+APPL????
diff --git a/other/bundle/server/Info.plist.in b/other/bundle/server/Info.plist.in
new file mode 100644
index 000000000..6cdd38f0d
--- /dev/null
+++ b/other/bundle/server/Info.plist.in
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+	<dict>
+		<key>CFBundleDevelopmentRegion</key>
+		<string>English</string>
+		<key>CFBundleExecutable</key>
+		<string>${TARGET_SERVER_LAUNCHER}</string>
+		<key>CFBundleIconFile</key>
+		<string>${TARGET_SERVER}</string>
+		<key>CFBundleInfoDictionaryVersion</key>
+		<string>6.0</string>
+		<key>CFBundlePackageType</key>
+		<string>APPL</string>
+		<key>CFBundleSignature</key>
+		<string>????</string>
+		<key>CFBundleVersion</key>
+		<string>${PROJECT_VERSION}</string>
+	</dict>
+</plist>
diff --git a/other/bundle/server/PkgInfo b/other/bundle/server/PkgInfo
new file mode 100644
index 000000000..6f749b0f3
--- /dev/null
+++ b/other/bundle/server/PkgInfo
@@ -0,0 +1 @@
+APPL????
diff --git a/other/freetype/freetype.lua b/other/freetype/freetype.lua
index bfd6523cc..f2517fd6c 100644
--- a/other/freetype/freetype.lua
+++ b/other/freetype/freetype.lua
@@ -36,9 +36,9 @@ FreeType = {
 			elseif option.use_winlib > 0 then
 				settings.cc.includes:Add(FreeType.basepath .. "/include")
 				if option.use_winlib == 32 then
-					settings.link.libpath:Add(FreeType.basepath .. "/lib/x86")
+					settings.link.libpath:Add(FreeType.basepath .. "/windows/lib32")
 				else
-					settings.link.libpath:Add(FreeType.basepath .. "/lib/x64")
+					settings.link.libpath:Add(FreeType.basepath .. "/windows/lib64")
 				end
 				settings.link.libs:Add("freetype")
 			end
diff --git a/other/icons/Teeworlds.icns b/other/icons/teeworlds.icns
similarity index 100%
rename from other/icons/Teeworlds.icns
rename to other/icons/teeworlds.icns
diff --git a/other/icons/Teeworlds.ico b/other/icons/teeworlds.ico
similarity index 100%
rename from other/icons/Teeworlds.ico
rename to other/icons/teeworlds.ico
diff --git a/other/icons/teeworlds.rc b/other/icons/teeworlds.rc
new file mode 100644
index 000000000..aa5dcb44f
--- /dev/null
+++ b/other/icons/teeworlds.rc
@@ -0,0 +1 @@
+ID ICON "teeworlds.ico"
diff --git a/other/icons/Teeworlds_srv.icns b/other/icons/teeworlds_srv.icns
similarity index 100%
rename from other/icons/Teeworlds_srv.icns
rename to other/icons/teeworlds_srv.icns
diff --git a/other/icons/Teeworlds_srv.ico b/other/icons/teeworlds_srv.ico
similarity index 100%
rename from other/icons/Teeworlds_srv.ico
rename to other/icons/teeworlds_srv.ico
diff --git a/other/icons/teeworlds_srv.rc b/other/icons/teeworlds_srv.rc
new file mode 100644
index 000000000..44287bb51
--- /dev/null
+++ b/other/icons/teeworlds_srv.rc
@@ -0,0 +1 @@
+ID ICON "teeworlds_srv.ico"
diff --git a/other/icons/teeworlds_srv_cl.rc b/other/icons/teeworlds_srv_cl.rc
index f7b046d92..8e18b64ce 100644
--- a/other/icons/teeworlds_srv_cl.rc
+++ b/other/icons/teeworlds_srv_cl.rc
@@ -1 +1 @@
-50h ICON "Teeworlds_srv.ico"
+50h ICON "teeworlds_srv.ico"
diff --git a/other/icons/teeworlds_srv_gcc.rc b/other/icons/teeworlds_srv_gcc.rc
index 3b7c78a56..44287bb51 100644
--- a/other/icons/teeworlds_srv_gcc.rc
+++ b/other/icons/teeworlds_srv_gcc.rc
@@ -1 +1 @@
-ID ICON "Teeworlds_srv.ico"
+ID ICON "teeworlds_srv.ico"
diff --git a/other/sdl/sdl.lua b/other/sdl/sdl.lua
index 19196ba2d..4bf5fe0f9 100644
--- a/other/sdl/sdl.lua
+++ b/other/sdl/sdl.lua
@@ -36,9 +36,9 @@ SDL = {
 			elseif option.use_winlib > 0 then
 				settings.cc.includes:Add(SDL.basepath .. "/include")
 				if option.use_winlib == 32 then
-					settings.link.libpath:Add(SDL.basepath .. "/lib/x86")
+					settings.link.libpath:Add(SDL.basepath .. "/windows/lib32")
 				else
-					settings.link.libpath:Add(SDL.basepath .. "/lib/x64")
+					settings.link.libpath:Add(SDL.basepath .. "/windows/lib64")
 				end
 				settings.link.libs:Add("SDL2")
 				settings.link.libs:Add("SDL2main")
diff --git a/scripts/darwin_change_dylib.py b/scripts/darwin_change_dylib.py
new file mode 100644
index 000000000..6f3922572
--- /dev/null
+++ b/scripts/darwin_change_dylib.py
@@ -0,0 +1,63 @@
+from collections import namedtuple
+import os
+import re
+import shlex
+import subprocess
+
+Config = namedtuple('Config', 'install_name_tool otool verbose')
+
+def dylib_regex(name):
+	return re.compile(r'\S*{}\S*'.format(re.escape(name)))
+
+class ChangeDylib:
+	def __init__(self, config):
+		self.config = config
+	def _check_call(self, process_args, *args, **kwargs):
+		if self.config.verbose >= 1:
+			print("EXECUTING {}".format(" ".join(shlex.quote(x) for x in process_args)))
+		if not (self.config.verbose >= 2 and "stdout" not in kwargs):
+			kwargs["stdout"] = open(os.devnull, 'wb')
+		return subprocess.check_call(process_args, *args, **kwargs)
+	def _check_output(self, process_args, *args, **kwargs):
+		if self.config.verbose >= 1:
+			print("EXECUTING {} FOR OUTPUT".format(" ".join(shlex.quote(x) for x in process_args)))
+		return subprocess.check_output(process_args, *args, **kwargs)
+	def _install_name_tool(self, *args):
+		return self._check_call((self.config.install_name_tool,) + args)
+	def _otool(self, *args):
+		return self._check_output((self.config.otool,) + args)
+
+	def change(self, filename, from_, to):
+		lines = self._otool("-L", filename).decode().splitlines()
+		regex = dylib_regex(from_)
+		matches = sum([regex.findall(l) for l in lines], [])
+		if len(matches) != 1:
+			if matches:
+				raise ValueError("More than one match found for {}: {}".format(from_, matches))
+			else:
+				raise ValueError("No matches found for {}".format(from_))
+		actual_from = matches[0]
+		self._install_name_tool("-change", actual_from, to, filename)
+
+def main():
+	import argparse
+	p = argparse.ArgumentParser(description="Manipulate shared library dependencies for macOS")
+
+	subcommands = p.add_subparsers(help="Subcommand", dest='command', metavar="COMMAND")
+	subcommands.required = True
+
+	change = subcommands.add_parser("change", help="Change a shared library dependency to a given value")
+	change.add_argument('-v', '--verbose', action='count', help="Verbose output")
+	change.add_argument('--tools', nargs=2, help="Paths to the install_name_tool and otool", default=("install_name_tool", "otool"))
+	change.add_argument('filename', metavar="FILE", help="Filename of the executable to manipulate")
+	change.add_argument('from_', metavar="FROM", help="Fuzzily matched library name to change")
+	change.add_argument('to', metavar="TO", help="Exact name that the library dependency should be changed to")
+	args = p.parse_args()
+
+	verbose = args.verbose or 0
+	install_name_tool, otool = args.tools
+	dylib = ChangeDylib(Config(install_name_tool, otool, verbose))
+	dylib.change(filename=args.filename, from_=args.from_, to=args.to)
+
+if __name__ == '__main__':
+	main()
diff --git a/scripts/dmg.py b/scripts/dmg.py
new file mode 100644
index 000000000..8f2d4cfc1
--- /dev/null
+++ b/scripts/dmg.py
@@ -0,0 +1,106 @@
+from collections import namedtuple
+import os
+import shlex
+import subprocess
+import tempfile
+
+ConfigDmgtools = namedtuple('Config', 'dmg hfsplus newfs_hfs verbose')
+ConfigHdiutil = namedtuple('Config', 'hdiutil verbose')
+
+def chunks(l, n):
+	"""
+	Yield successive n-sized chunks from l.
+	
+	From https://stackoverflow.com/a/312464.
+	"""
+	for i in range(0, len(l), n):
+		yield l[i:i + n]
+
+class Dmg:
+	def __init__(self, config):
+		self.config = config
+
+	def _check_call(self, process_args, *args, **kwargs):
+		if self.config.verbose >= 1:
+			print("EXECUTING {}".format(" ".join(shlex.quote(x) for x in process_args)))
+		if not (self.config.verbose >= 2 and "stdout" not in kwargs):
+			kwargs["stdout"] = open(os.devnull, 'wb')
+		subprocess.check_call(process_args, *args, **kwargs)
+
+class Dmgtools(Dmg):
+	def _mkfs_hfs(self, *args):
+		self._check_call((self.config.newfs_hfs,) + args)
+	def _hfs(self, *args):
+		self._check_call((self.config.hfsplus,) + args)
+	def _dmg(self, *args):
+		self._check_call((self.config.dmg,) + args)
+
+	def _create_hfs(self, hfs, volume_name, size):
+		if self.config.verbose >= 1:
+			print("TRUNCATING {} to {} bytes".format(hfs, size))
+		with open(hfs, 'wb') as f:
+			f.truncate(size)
+		self._mkfs_hfs('-v', volume_name, hfs)
+
+	def _symlink(self, hfs, target, link_name):
+		self._hfs(hfs, 'symlink', link_name, target)
+
+	def _add(self, hfs, directory):
+		self._hfs(hfs, 'addall', directory)
+
+	def _finish(self, hfs, dmg):
+		self._dmg('build', hfs, dmg)
+
+	def create(self, dmg, volume_name, directory, symlinks):
+		input_size = sum(os.stat(os.path.join(path, f)).st_size for path, dirs, files in os.walk(directory) for f in files)
+		output_size = max(input_size * 2, 1024**2)
+		hfs = tempfile.mktemp(prefix=dmg + '.', suffix='.hfs')
+		self._create_hfs(hfs, volume_name, output_size)
+		self._add(hfs, directory)
+		for target, link_name in symlinks:
+			self._symlink(hfs, target, link_name)
+		self._finish(hfs, dmg)
+		if self.config.verbose >= 1:
+			print("REMOVING {}".format(hfs))
+		os.remove(hfs)
+
+class Hdiutil(Dmg):
+	def _hdiutil(self, *args):
+		self._check_call((self.config.hdiutil,) + args)
+
+	def create(self, dmg, volume_name, directory, symlinks):
+		if symlinks:
+			raise NotImplementedError("symlinks are not yet implemented")
+		self._hdiutil('create', '-volname', volume_name, '-srcdir', directory, dmg)
+
+def main():
+	import argparse
+	p = argparse.ArgumentParser(description="Manipulate dmg archives")
+
+	subcommands = p.add_subparsers(help="Subcommand", dest='command', metavar="COMMAND")
+	subcommands.required = True
+
+	create = subcommands.add_parser("create", help="Create a dmg archive from files or directories")
+	create.add_argument('-v', '--verbose', action='count', help="Verbose output")
+	createx = create.add_mutually_exclusive_group(required=True)
+	createx.add_argument('--dmgtools', nargs=3, help="Paths to the dmg and hfsplus executable (https://github.com/mozilla/libdmg-hfsplus) and the newfs_hfs executable (http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/diskdev_cmds-540.1.linux3.tar.gz/0435afc389b919027b69616ad1b05709/diskdev_cmds-540.1.linux3.tar.gz)")
+	createx.add_argument('--hdiutil', help="Path to the hdiutil (only exists for macOS at time of writing)")
+	create.add_argument('output', metavar="OUTPUT", help="Filename of the output dmg archive")
+	create.add_argument('volume_name', metavar="VOLUME_NAME", help="Name of the dmg archive")
+	create.add_argument('directory', metavar="DIR", help="Directory to create the archive from")
+	create.add_argument('--symlink', metavar="SYMLINK", nargs=2, action="append", help="Symlink the first argument under the second name")
+	args = p.parse_args()
+
+	verbose = args.verbose or 0
+	symlinks = args.symlink or []
+	if args.dmgtools:
+		dmg, hfsplus, newfs_hfs = args.dmgtools
+		dmg = Dmgtools(ConfigDmgtools(dmg=dmg, hfsplus=hfsplus, newfs_hfs=newfs_hfs, verbose=verbose))
+	elif args.hdiutil:
+		dmg = Hdiutil(ConfigHdiutil(hdiutil=args.hdiutil, verbose=verbose))
+	else:
+		raise RuntimeError("unreachable")
+	dmg.create(volume_name=args.volume_name, directory=args.directory, dmg=args.output, symlinks=symlinks)
+
+if __name__ == '__main__':
+	main()
diff --git a/scripts/git_revision.py b/scripts/git_revision.py
new file mode 100644
index 000000000..ae1186c42
--- /dev/null
+++ b/scripts/git_revision.py
@@ -0,0 +1,21 @@
+import errno
+import subprocess
+try:
+	from subprocess import DEVNULL
+except ImportError:
+	import os
+	DEVNULL = open(os.devnull, 'wb')
+try:
+	FileNotFoundError
+except NameError:
+	FileNotFoundError = OSError
+try:
+	git_hash = subprocess.check_output(["git", "rev-parse", "--short=16", "HEAD"], stderr=DEVNULL).decode().strip()
+	definition = '"{}"'.format(git_hash)
+except FileNotFoundError as e:
+	if e.errno != errno.ENOENT:
+		raise
+	definition = "0"
+except subprocess.CalledProcessError:
+	definition = "0";
+print("const char *GIT_SHORTREV_HASH = {};".format(definition))
diff --git a/src/base/system.c b/src/base/system.c
index bcab1771e..92e090622 100644
--- a/src/base/system.c
+++ b/src/base/system.c
@@ -334,6 +334,11 @@ unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
 	return fread(buffer, 1, size, (FILE*)io);
 }
 
+unsigned io_unread_byte(IOHANDLE io, unsigned char byte)
+{
+	return ungetc(byte, (FILE*)io) == EOF;
+}
+
 unsigned io_skip(IOHANDLE io, int size)
 {
 	fseek((FILE*)io, size, SEEK_CUR);
@@ -1439,6 +1444,10 @@ int fs_storage_path(const char *appname, char *path, int max)
 	return 0;
 #else
 	char *home = getenv("HOME");
+	int i;
+	char *xdgdatahome = getenv("XDG_DATA_HOME");
+	char xdgpath[max];
+	
 	if(!home)
 		return -1;
 	
@@ -1447,10 +1456,6 @@ int fs_storage_path(const char *appname, char *path, int max)
 	return 0;
 #endif
 
-	int i;
-	char *xdgdatahome = getenv("XDG_DATA_HOME");
-	char xdgpath[max];
-	
 	/* old folder location */
 	snprintf(path, max, "%s/.%s", home, appname);
 	for(i = strlen(home)+2; path[i]; i++)
diff --git a/src/base/system.h b/src/base/system.h
index 3f842279e..318b4f3d9 100644
--- a/src/base/system.h
+++ b/src/base/system.h
@@ -212,6 +212,21 @@ IOHANDLE io_open(const char *filename, int flags);
 */
 unsigned io_read(IOHANDLE io, void *buffer, unsigned size);
 
+/*
+	Function: io_unread_byte
+		"Unreads" a single byte, making it available for future read
+		operations.
+
+	Parameters:
+		io - Handle to the file to unread the byte from.
+		byte - Byte to unread.
+
+	Returns:
+		Returns 0 on success and 1 on failure.
+
+*/
+unsigned io_unread_byte(IOHANDLE io, unsigned char byte);
+
 /*
 	Function: io_skip
 		Skips data in a file.
diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp
index 1bf9ae531..81de48b0b 100644
--- a/src/engine/client/backend_sdl.cpp
+++ b/src/engine/client/backend_sdl.cpp
@@ -16,7 +16,7 @@
 #if defined(CONF_FAMILY_WINDOWS)
 	PFNGLTEXIMAGE3DPROC glTexImage3DInternal;
 
-	GLAPI void GLAPIENTRY glTexImage3D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
+	void GLAPIENTRY glTexImage3D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
 	{
 		glTexImage3DInternal(target, level, internalFormat, width, height, depth, border, format, type, pixels);
 	}
diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp
index 7807fc8ab..abf00ad83 100644
--- a/src/engine/client/graphics_threaded.cpp
+++ b/src/engine/client/graphics_threaded.cpp
@@ -6,7 +6,8 @@
 #include <base/tl/threading.h>
 
 #include <base/system.h>
-#include <engine/external/pnglite/pnglite.h>
+
+#include <pnglite.h>
 
 #include <engine/shared/config.h>
 #include <engine/graphics.h>
diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp
index 6d7088f33..0c627640b 100644
--- a/src/engine/client/sound.cpp
+++ b/src/engine/client/sound.cpp
@@ -12,8 +12,9 @@
 
 #include "sound.h"
 
-extern "C" { // wavpack
-	#include <engine/external/wavpack/wavpack.h>
+extern "C"
+{
+	#include <wavpack.h>
 }
 #include <math.h>
 
@@ -68,6 +69,8 @@ static int m_NextVoice = 0;
 static int *m_pMixBuffer = 0;	// buffer only used by the thread callback function
 static unsigned m_MaxFrames = 0;
 
+static IOHANDLE s_File;
+
 // TODO: there should be a faster way todo this
 static short Int2Short(int i)
 {
@@ -325,11 +328,43 @@ void CSound::RateConvert(int SampleID)
 	pSample->m_NumFrames = NumFrames;
 }
 
-int CSound::ReadData(void *pBuffer, int Size)
+static int ReadDataOld(void *pBuffer, int Size)
+{
+	return io_read(s_File, pBuffer, Size);
+}
+
+#if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX)
+static int ReadData(void *pId, void *pBuffer, int Size)
+{
+	(void)pId;
+	return ReadDataOld(pBuffer, Size);
+}
+
+static int ReturnFalse(void *pId)
+{
+	(void)pId;
+	return 0;
+}
+
+static unsigned int GetPos(void *pId)
+{
+	(void)pId;
+	return io_tell(s_File);
+}
+
+static unsigned int GetLength(void *pId)
 {
-	return io_read(ms_File, pBuffer, Size);
+	(void)pId;
+	return io_length(s_File);
 }
 
+static int PushBackByte(void *pId, int Char)
+{
+	(void)pId;
+	return io_unread_byte(s_File, Char);
+}
+#endif
+
 ISound::CSampleHandle CSound::LoadWV(const char *pFilename)
 {
 	CSample *pSample;
@@ -349,8 +384,8 @@ ISound::CSampleHandle CSound::LoadWV(const char *pFilename)
 		return CSampleHandle();
 
 	lock_wait(m_SoundLock);
-	ms_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_ALL);
-	if(!ms_File)
+	s_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_ALL);
+	if(!s_File)
 	{
 		dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename);
 		lock_unlock(m_SoundLock);
@@ -360,14 +395,24 @@ ISound::CSampleHandle CSound::LoadWV(const char *pFilename)
 	SampleID = AllocID();
 	if(SampleID < 0)
 	{
-		io_close(ms_File);
-		ms_File = 0;
+		io_close(s_File);
+		s_File = 0;
 		lock_unlock(m_SoundLock);
 		return CSampleHandle();
 	}
 	pSample = &m_aSamples[SampleID];
 
-	pContext = WavpackOpenFileInput(ReadData, aError);
+#if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX)
+	WavpackStreamReader Callback = {0};
+	Callback.can_seek = ReturnFalse;
+	Callback.get_length = GetLength;
+	Callback.get_pos = GetPos;
+	Callback.push_back_byte = PushBackByte;
+	Callback.read_bytes = ReadData;
+	pContext = WavpackOpenFileInputEx(&Callback, (void *)1, 0, aError, 0, 0);
+#else
+	pContext = WavpackOpenFileInput(ReadDataOld, aError);
+#endif
 	if (pContext)
 	{
 		int m_aSamples = WavpackGetNumSamples(pContext);
@@ -385,8 +430,8 @@ ISound::CSampleHandle CSound::LoadWV(const char *pFilename)
 		if(pSample->m_Channels > 2)
 		{
 			dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", pFilename);
-			io_close(ms_File);
-			ms_File = 0;
+			io_close(s_File);
+			s_File = 0;
 			lock_unlock(m_SoundLock);
 			return CSampleHandle();
 		}
@@ -401,8 +446,8 @@ ISound::CSampleHandle CSound::LoadWV(const char *pFilename)
 		if(BitsPerSample != 16)
 		{
 			dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", BitsPerSample, pFilename);
-			io_close(ms_File);
-			ms_File = 0;
+			io_close(s_File);
+			s_File = 0;
 			lock_unlock(m_SoundLock);
 			return CSampleHandle();
 		}
@@ -429,8 +474,8 @@ ISound::CSampleHandle CSound::LoadWV(const char *pFilename)
 		dbg_msg("sound/wv", "failed to open %s: %s", pFilename, aError);
 	}
 
-	io_close(ms_File);
-	ms_File = NULL;
+	io_close(s_File);
+	s_File = NULL;
 
 	if(g_Config.m_Debug)
 		dbg_msg("sound/wv", "loaded %s", pFilename);
@@ -560,7 +605,4 @@ bool CSound::IsPlaying(CSampleHandle SampleID)
 	return Ret;
 }
 
-IOHANDLE CSound::ms_File = 0;
-
 IEngineSound *CreateEngineSound() { return new CSound; }
-
diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h
index ff357c047..cec2cdeda 100644
--- a/src/engine/client/sound.h
+++ b/src/engine/client/sound.h
@@ -21,10 +21,6 @@ public:
 
 	static void RateConvert(int SampleID);
 
-	// TODO: Refactor: clean this mess up
-	static IOHANDLE ms_File;
-	static int ReadData(void *pBuffer, int Size);
-
 	virtual bool IsSoundEnabled() { return m_SoundEnabled != 0; }
 
 	virtual CSampleHandle LoadWV(const char *pFilename);
diff --git a/src/osxlaunch/server.mm b/src/osxlaunch/server.mm
new file mode 100644
index 000000000..5ad2d4eda
--- /dev/null
+++ b/src/osxlaunch/server.mm
@@ -0,0 +1,112 @@
+#import <Cocoa/Cocoa.h>
+
+@interface ServerView : NSTextView
+{
+	NSTask *task;
+	NSFileHandle *file;
+}
+- (void)listenTo: (NSTask*)t;
+@end
+
+@implementation ServerView
+- (void)listenTo: (NSTask*)t;
+{
+	NSPipe *pipe;
+	task = t;
+	pipe = [NSPipe pipe];
+	[task setStandardOutput: pipe];
+	file = [pipe fileHandleForReading];
+
+	[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(outputNotification:) name: NSFileHandleReadCompletionNotification object: file];
+
+	[file readInBackgroundAndNotify];
+}
+
+- (void) outputNotification: (NSNotification *) notification
+{
+	NSData *data = [[[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] retain];
+	NSString *string = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding];
+	NSAttributedString *attrstr = [[NSAttributedString alloc] initWithString: string];
+
+	[[self textStorage] appendAttributedString: attrstr];
+	int length = [[self textStorage] length];
+	NSRange range = NSMakeRange(length, 0);
+	[self scrollRangeToVisible: range];
+
+	[attrstr release];
+	[string release];
+	[file readInBackgroundAndNotify];
+}
+
+-(void)windowWillClose:(NSNotification *)notification
+{
+	[task terminate];
+	[NSApp terminate:self];
+}
+@end
+
+void runServer()
+{
+	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+	NSApp = [NSApplication sharedApplication];
+	NSBundle* mainBundle = [NSBundle mainBundle];
+	NSTask *task;
+	task = [[NSTask alloc] init];
+	[task setCurrentDirectoryPath: [mainBundle resourcePath]];
+
+	// get a server config
+	NSOpenPanel* openDlg = [NSOpenPanel openPanel];
+	[openDlg setCanChooseFiles:YES];
+
+	if([openDlg runModalForDirectory:nil file:nil] != NSOKButton)
+		return;
+
+	NSArray* filenames = [openDlg filenames];
+	if([filenames count] != 1)
+		return;
+
+	NSString* filename = [filenames objectAtIndex: 0];
+	NSArray* arguments = [NSArray arrayWithObjects: @"-f", filename, nil];
+
+	// run server
+	NSWindow *window;
+	ServerView *view;
+	NSRect graphicsRect;
+
+	graphicsRect = NSMakeRect(100.0, 1000.0, 600.0, 400.0);
+
+	window = [[NSWindow alloc]
+		initWithContentRect: graphicsRect
+		styleMask: NSTitledWindowMask
+		| NSClosableWindowMask
+		| NSMiniaturizableWindowMask
+		backing: NSBackingStoreBuffered
+		defer: NO];
+
+	[window setTitle: @"Teeworlds Server"];
+
+	view = [[[ServerView alloc] initWithFrame: graphicsRect] autorelease];
+	[view setEditable: NO];
+	[view setRulerVisible: YES];
+
+	[window setContentView: view];
+	[window setDelegate: (id<NSWindowDelegate>)view];
+	[window makeKeyAndOrderFront: nil];
+
+	[view listenTo: task];
+	[task setLaunchPath: [mainBundle pathForAuxiliaryExecutable: @"teeworlds_srv"]];
+	[task setArguments: arguments];
+	[task launch];
+	[NSApp run];
+	[task terminate];
+
+	[NSApp release];
+	[pool release];
+}
+
+int main (int argc, char **argv)
+{
+	runServer();
+
+	return 0;
+}
-- 
2.20.1