about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--data/languages/belarusian.txt679
-rw-r--r--data/languages/hungarian.txt679
-rw-r--r--data/languages/index.txt8
-rw-r--r--data/languages/ukrainian.txt16
-rw-r--r--scripts/cmd5.py2
-rw-r--r--src/base/system.c44
-rw-r--r--src/engine/client/client.cpp202
-rw-r--r--src/engine/client/client.h30
-rw-r--r--src/engine/client/friends.cpp1
-rw-r--r--src/engine/console.h7
-rw-r--r--src/engine/server.h2
-rw-r--r--src/engine/server/server.cpp53
-rw-r--r--src/engine/server/server.h6
-rw-r--r--src/engine/shared/config_variables.h9
-rw-r--r--src/engine/shared/console.cpp32
-rw-r--r--src/engine/shared/console.h12
-rw-r--r--src/engine/shared/datafile.cpp113
-rw-r--r--src/engine/shared/datafile.h16
-rw-r--r--src/engine/shared/econ.cpp173
-rw-r--r--src/engine/shared/econ.h50
-rw-r--r--src/engine/shared/filecollection.cpp186
-rw-r--r--src/engine/shared/filecollection.h35
-rw-r--r--src/engine/shared/network.h84
-rw-r--r--src/engine/shared/network_console.cpp219
-rw-r--r--src/engine/shared/network_console_conn.cpp186
-rw-r--r--src/engine/shared/network_server.cpp12
-rw-r--r--src/game/client/components/console.cpp14
-rw-r--r--src/game/client/components/console.h2
-rw-r--r--src/game/client/components/countryflags.cpp2
-rw-r--r--src/game/client/components/countryflags.h3
-rw-r--r--src/game/client/components/menus.cpp5
-rw-r--r--src/game/client/components/menus_settings.cpp5
-rw-r--r--src/game/client/lineinput.cpp2
-rw-r--r--src/game/editor/editor.cpp2
-rw-r--r--src/game/gamecore.cpp48
-rw-r--r--src/game/layers.cpp2
-rw-r--r--src/game/server/entities/character.cpp2
-rw-r--r--src/game/server/entities/laser.cpp8
-rw-r--r--src/game/server/gamecontroller.cpp1
-rw-r--r--src/game/version.h2
40 files changed, 2595 insertions, 359 deletions
diff --git a/data/languages/belarusian.txt b/data/languages/belarusian.txt
new file mode 100644
index 00000000..672995c0
--- /dev/null
+++ b/data/languages/belarusian.txt
@@ -0,0 +1,679 @@
+
+##### translated strings #####
+
+%d Bytes
+== %d байт
+
+%d of %d servers, %d players
+== %d з %d сервераў, %d гульцоў
+
+%d%% loaded
+== %d%% загружана
+
+%ds left
+== засталося %d сек.
+
+%i minute left
+== Засталася %i хвіліна!
+
+%i minutes left
+== Засталося %i хвілін!
+
+%i second left
+== Засталася %i секунда!
+
+%i seconds left
+== Засталося %i секунд!
+
+%s wins!
+== %s перамог!
+
+-Page %d-
+== -Старонка %d-
+
+Abort
+== Адмена
+
+Add
+== Дадаць
+
+Add Friend
+== Дадаць сябра
+
+Address
+== Адрас
+
+All
+== Усё
+
+Alpha
+== Празрыст.
+
+Always show name plates
+== Заўсёды паказваць нікі гульцоў
+
+Are you sure that you want to delete the demo?
+== Вы ўпэўнены, што жадаеце выдаліць дэма?
+
+Are you sure that you want to quit?
+== Вы сапраўды жадаеце выйсці?
+
+Are you sure that you want to remove the player from your friends list?
+== Вы ўпэўнены, што жадаеце выдаліць гульца з сяброў?
+
+As this is the first time you launch the game, please enter your nick name below. It's recommended that you check the settings to adjust them to your liking before joining a server.
+== Бо гэта ваш першы запуск гульні, калі ласка, увядзіце свой нік у поле ніжэй. Таксама рекоммендуется праверыць налады гульні і памяняць некаторыя з іх перад тым, як пачаць гуляць.
+
+Automatically record demos
+== Аўтаматычна запісваць дэма
+
+Automatically take game over screenshot
+== Рабіць здымак вынікаў гульні
+
+Blue team
+== Сінія
+
+Blue team wins!
+== Сінія перамаглі!
+
+Body
+== Цела
+
+Call vote
+== Галасаваць
+
+Change settings
+== Змяніць налады
+
+Chat
+== Чат
+
+Clan
+== Клан
+
+Client
+== Кліент
+
+Close
+== Выйсці
+
+Compatible version
+== Сумяшчальная версія
+
+Connect
+== Падлучыцца
+
+Connecting to
+== Падлучэнне да
+
+Connection Problems...
+== Праблемы з сувяззю...
+
+Console
+== Кансоль
+
+Controls
+== Кіраванне
+
+Count players only
+== Лічыць толькі гульцоў
+
+Country
+== Сцяг вашай краіны
+
+Crc:
+== Crc:
+
+Created:
+== Створаны:
+
+Current
+== Бягучы
+
+Current version: %s
+== Бягучая версія: %s
+
+Custom colors
+== Свае колеры
+
+Delete
+== Выдаліць
+
+Delete demo
+== Выдаліць дэма
+
+Demo details
+== Дэталі дэма
+
+Demofile: %s
+== Дэма: %s
+
+Demos
+== Дэма
+
+Disconnect
+== Адключыць
+
+Disconnected
+== Адключана
+
+Display Modes
+== Дазвол экрана
+
+Downloading map
+== Спампоўка карты
+
+Draw!
+== Нічыя!
+
+Dynamic Camera
+== Дынамічная камера
+
+Emoticon
+== Эмоцыі
+
+Enter
+== Уваход
+
+Error
+== Памылка
+
+Error loading demo
+== памылка пры загрузцы дэма
+
+FSAA samples
+== Сэмплаў FSAA
+
+Favorite
+== Абраны
+
+Favorites
+== Абраныя
+
+Feet
+== Ногі
+
+Filter
+== Фільтр
+
+Fire
+== Стрэл
+
+Folder
+== Тэчка
+
+Force vote
+== Фарсіраваць
+
+Free-View
+== Вольны агляд
+
+Friends
+== Сябры
+
+Fullscreen
+== Поўнаэкранны рэжым
+
+Game
+== Гульня
+
+Game info
+== Інфа пра гульню
+
+Game over
+== Гульня скончана
+
+Game type
+== Тып гульні
+
+Game types:
+== Тып гульні:
+
+General
+== Асноўныя
+
+Graphics
+== Графіка
+
+Grenade
+== Гранатамёт
+
+Hammer
+== Молат
+
+Has people playing
+== Не пусты сервер
+
+High Detail
+== Высокая дэталізацыя
+
+Hook
+== Крук
+
+Host address
+== Адрас сервера
+
+Hue
+== Адценне
+
+Info
+== Інфа
+
+Internet
+== Інтэрнэт
+
+Invalid Demo
+== Недапушчальнае дэма
+
+Join blue
+== За сініх
+
+Join game
+== Гуляць
+
+Join red
+== За чырвоных
+
+Jump
+== Скачок
+
+Kick player
+== Забаніць гульца
+
+LAN
+== LAN
+
+Language
+== Мова
+
+Length:
+== Даўжыня
+
+Lht.
+== Яркасць
+
+Loading
+== Загрузка
+
+MOTD
+== MOTD
+
+Map
+== Карта
+
+Map:
+== Карта:
+
+Max Screenshots
+== Максімальная колькасць здымкаў
+
+Max demos
+== Максімальная колькасць дэма
+
+Maximum ping:
+== Макс. пінг:
+
+Miscellaneous
+== Дадаткова
+
+Mouse sens.
+== Адчув. мышы
+
+Move left
+== Крок налева
+
+Move player to spectators
+== Зрабіць назіральнікам
+
+Move right
+== Крок направа
+
+Movement
+== Перасоўванне
+
+Mute when not active
+== Глушыць гукі, калі гульня неактыўная
+
+Name
+== Імя
+
+Name plates size
+== Памер
+
+Netversion:
+== Версія:
+
+New name:
+== Новае імя
+
+News
+== Навіны
+
+Next weapon
+== След. зброя
+
+Nickname
+== Нік
+
+No
+== Не
+
+No password
+== Без пароля
+
+No servers found
+== Сервера не знойдзены
+
+No servers match your filter criteria
+== Няма сервераў, падыходных пад ваш фільтр
+
+Ok
+== ОК
+
+Open
+== Адкрыць
+
+Parent Folder
+== Бацькоўскі каталог
+
+Password
+== Пароль
+
+Password incorrect
+== Пароль
+
+Ping
+== Пінг
+
+Pistol
+== Пісталет
+
+Play
+== Прагляд
+
+Play background music
+== Гуляць фонавую музыку
+
+Player
+== Гулец
+
+Player country:
+== Краіна:
+
+Player options
+== Опцыі гульца
+
+Players
+== Гульцы
+
+Please balance teams!
+== Збалансуйце каманды!
+
+Prev. weapon
+== Прад. зброя
+
+Quality Textures
+== Якасныя тэкстуры
+
+Quick search:
+== Хуткі пошук:
+
+Quit
+== Выйсце
+
+Quit anyway?
+== Выйсці?
+
+REC %3d:%02d
+== REC %3d:%02d
+
+Reason:
+== Чыннік:
+
+Record demo
+== Запісаць дэма
+
+Red team
+== Чырвоныя
+
+Red team wins!
+== Чырвоныя перамаглі!
+
+Refresh
+== Абнавіць
+
+Refreshing master servers
+== Абнаўленне спісу майстар-сервераў
+
+Remote console
+== Кансоль сервера
+
+Remove
+== Выдаліць
+
+Remove friend
+== Выдаліць сябра
+
+Rename
+== Пераназв.
+
+Rename demo
+== Пераназваць дэма
+
+Reset filter
+== Скінуць фільтры
+
+Reset to defaults
+== Скінуць налады
+
+Rifle
+== Бласцер
+
+Round
+== Раўнд
+
+Sample rate
+== Чашчыня
+
+Sat.
+== Кантраст
+
+Score
+== Ачкі
+
+Score board
+== Табло
+
+Score limit
+== Ліміт ачкоў
+
+Scoreboard
+== Табло
+
+Screenshot
+== Здымак
+
+Server address:
+== Адрас сервера
+
+Server details
+== Дэталі сервера
+
+Server filter
+== Фільтр сервераў
+
+Server info
+== Інфармацыя
+
+Server not full
+== Сервер не запоўнены
+
+Settings
+== Налады
+
+Shotgun
+== Драбавік
+
+Show chat
+== Паказаць чат
+
+Show friends only
+== Толькі з сябрамі
+
+Show ingame HUD
+== Паказваць нутрагульнявы HUD
+
+Show name plates
+== Паказваць нікі гульцоў
+
+Show only supported
+== Паказваць толькі падтрымоўваныя дазволы экрана
+
+Size:
+== Памер:
+
+Skins
+== Скіны
+
+Sound
+== Гук
+
+Sound error
+== Гукавая памылка
+
+Sound volume
+== Гучнасць
+
+Spectate
+== Назіраць
+
+Spectate next
+== Назіраць наст.
+
+Spectate previous
+== Назіраць папяр.
+
+Spectator mode
+== Назіральнік
+
+Spectators
+== Назіральнікі
+
+Standard gametype
+== Стандартны тып гульні
+
+Standard map
+== Стандартная карта
+
+Stop record
+== Стоп
+
+Strict gametype filter
+== Строгі фільтр рэжым.
+
+Sudden Death
+== Хуткая смерць
+
+Switch weapon on pickup
+== Перамыкаць зброю пры падборы
+
+Team
+== Каманда
+
+Team chat
+== Камандны чат
+
+Teeworlds %s is out! Download it at www.teeworlds.com!
+== Выйшла Teeworlds %s! Спампоўвайце на www.teeworlds.com!
+
+Texture Compression
+== Сціск тэкстур
+
+The audio device couldn't be initialised.
+== Аўдыё прылада не можа быць ініцыялізавана
+
+The server is running a non-standard tuning on a pure game type.
+== Сервер запушчаны з нестандартнымі наладамі на стандартным тыпе гульні.
+
+There's an unsaved map in the editor, you might want to save it before you quit the game.
+== Ёсць незахаваная карта ў рэдактары, Вы можаце захаваць яе перад тым, як выйсці.
+
+Time limit
+== Ліміт часу
+
+Time limit: %d min
+== Ліміт часу: %d
+
+Try again
+== ОК
+
+Type
+== Тып
+
+Type:
+== Тып:
+
+UI Color
+== Колер інтэрфейсу
+
+Unable to delete the demo
+== Немагчыма выдаліць дэма
+
+Unable to rename the demo
+== Немагчыма пераназваць дэма
+
+Use sounds
+== Выкарыстоўваць гукі
+
+Use team colors for name plates
+== Камандныя колеры для нікаў гульцоў
+
+V-Sync
+== Вертыкальная сінхранізацыя
+
+Version
+== Версія
+
+Version:
+== Версія:
+
+Vote command:
+== Камманда галасавання:
+
+Vote description:
+== Апісанне галасавання:
+
+Vote no
+== Супраць
+
+Vote yes
+== За
+
+Voting
+== Галасаванне
+
+Warmup
+== Размінка
+
+Weapon
+== Зброя
+
+Welcome to Teeworlds
+== Сардэчна запрашаем у Teeworlds!
+
+Yes
+== Так
+
+You must restart the game for all settings to take effect.
+== Перазапусціце гульню для ўжывання змен.
+
+Your skin
+== Ваш скін
+
+no limit
+== Без ліміту
+
+##### needs translation #####
+
+##### old translations #####
+
diff --git a/data/languages/hungarian.txt b/data/languages/hungarian.txt
new file mode 100644
index 00000000..49f3116a
--- /dev/null
+++ b/data/languages/hungarian.txt
@@ -0,0 +1,679 @@
+
+##### translated strings #####
+
+%d Bytes
+== %d Bit
+
+%d of %d servers, %d players
+== %d Szerver, %d Játékos
+
+%d%% loaded
+== %d%% betöltve
+
+%ds left
+==  %ds vissza
+
+%i minute left
+== %i perc vissza
+
+%i minutes left
+== %i perc vissza
+
+%i second left
+== %i másodperc vissza
+
+%i seconds left
+== %i másodperc vissza
+
+%s wins!
+== %s nyert!
+
+-Page %d-
+== -oldal %d-
+
+Abort
+== Mégse
+
+Add
+== Hozzáad
+
+Add Friend
+== Hozzáad barátot
+
+Address
+== Cím
+
+All
+== Mindenki
+
+Alpha
+== Alpha
+
+Always show name plates
+== Mindig mutassa a névtáblát
+
+Are you sure that you want to delete the demo?
+== Biztos hogy le akarod törölni a demót?
+
+Are you sure that you want to quit?
+== Biztos hogy ki akarsz lépni?
+
+Are you sure that you want to remove the player from your friends list?
+== Biztos vagy benne hogy ki akarod törölni a játékost a barátok listájáról?
+
+As this is the first time you launch the game, please enter your nick name below. It's recommended that you check the settings to adjust them to your liking before joining a server.
+== Mivel ez az első alkalom hogy elindítottad ezt a játékot, kérlekk add meg a neved!
+
+Automatically record demos
+== Magától rögzítse a demókat
+
+Automatically take game over screenshot
+== Magától készítsen a játék végén képet
+
+Blue team
+== Kék csapat
+
+Blue team wins!
+== A kék csapat nyert!
+
+Body
+== Test
+
+Call vote
+== Szavazás
+
+Change settings
+== Beállítások átállítása
+
+Chat
+== Chat
+
+Clan
+== Klán
+
+Client
+== Kliens
+
+Close
+== Bezárás
+
+Compatible version
+== Kompatibilis Verzió
+
+Connect
+== Csatlakozás
+
+Connecting to
+== Csatlakozás a
+
+Connection Problems...
+== Csatlakozási problémák...
+
+Console
+== Konzol
+
+Controls
+== Irányítás
+
+Count players only
+== Csak számontartott játékosok
+
+Country
+== Ország
+
+Crc:
+== Crc:
+
+Created:
+== Készítette:
+
+Current
+== Aktuális
+
+Current version: %s
+== Aktuális verzió: %s
+
+Custom colors
+== Egyéni színek
+
+Delete
+== Törlés
+
+Delete demo
+== Demó törlése
+
+Demo details
+== Információk a demóról
+
+Demos
+== Demók
+
+Disconnect
+== Szerver elhagyása
+
+Disconnected
+== Kilépett
+
+Display Modes
+== Kijelző módok
+
+Downloading map
+== Pálya letöltése
+
+Draw!
+== Rajzolj!
+
+Dynamic Camera
+== Dinamikus kamera
+
+Emoticon
+== Hangulatjel
+
+Enter
+== Belépés
+
+Error
+== Hiba
+
+Error loading demo
+== Hiba a demó betöltésében
+
+FSAA samples
+== FSAA mintáku
+
+Favorite
+== Kedvenc
+
+Favorites
+== Kedvencek
+
+Feet
+== Láb
+
+Filter
+== Szűrő
+
+Fire
+== Tűz
+
+Folder
+== Mappa
+
+Force vote
+== Különleges szavazás
+
+Free-View
+== Szabad-nézet
+
+Friends
+== Barátok
+
+Fullscreen
+== Teljesképernyő
+
+Game
+== Játék
+
+Game info
+== Játék infó
+
+Game over
+== Játék vége
+
+Game type
+== Játék fajtája
+
+Game types:
+== Játék fajtái:
+
+General
+== Általános
+
+Graphics
+== Grafika
+
+Grenade
+== Gránát
+
+Hammer
+== Kalapács
+
+Has people playing
+== Játékos játszik
+
+High Detail
+== Jó részletek
+
+Hook
+== Horog
+
+Host address
+== Szerver címe
+
+Hue
+== Színárnyalat
+
+Info
+== Infó
+
+Internet
+== Internet
+
+Invalid Demo
+== Érvénytelen demó
+
+Join blue
+== Kékhez lépés
+
+Join game
+== Csatlakozás a játékhoz
+
+Join red
+== Piroshoz lépés
+
+Jump
+== Ugrás
+
+Kick player
+== Játékos kirúgása
+
+LAN
+== Helyi
+
+Language
+== Nyelv
+
+Length:
+== Hossza:
+
+Lht.
+== Lht.
+
+Loading
+== Betöltés
+
+MOTD
+== Napi üzenet
+
+Map
+== Pálya
+
+Map:
+== Pálya:
+
+Max Screenshots
+== Maximum Fotó
+
+Max demos
+== Maximum Demó
+
+Maximum ping:
+== Maximum Ping:
+
+Miscellaneous
+== Vegyes
+
+Mouse sens.
+== Egér érzékenysége
+
+Move left
+== Balra lépés
+
+Move player to spectators
+== Megfigyelő lett
+
+Move right
+== Jobbra lépés
+
+Movement
+== Mozgás
+
+Mute when not active
+== Letiltás ha nem aktív
+
+Name
+== Név
+
+Name plates size
+== Névtáblák mérete
+
+Netversion:
+== Netes verzió:
+
+New name:
+== Új Név:
+
+News
+== Hírek
+
+Next weapon
+== Következő fegyver
+
+Nickname
+== Becenév
+
+No
+== Nem
+
+No password
+== Jelszó nélküli
+
+No servers found
+== Nem talált szervereket
+
+No servers match your filter criteria
+== Nincs szerver a szűrőfeltételeidhez
+
+Ok
+== Oké
+
+Open
+== Megnyit
+
+Parent Folder
+== Szülői mappa
+
+Password
+== Jelszó
+
+Password incorrect
+== Helytelen jelszó
+
+Ping
+== Ping
+
+Pistol
+== Pisztoly
+
+Play
+== Játék
+
+Player
+== Játékos
+
+Player options
+== Játékos beállításai
+
+Players
+== Játékosok
+
+Please balance teams!
+== Kérlek egyenlítsd ki a csapatokat!
+
+Prev. weapon
+== Előző fegyver
+
+Quality Textures
+== Minőségi kidolgozás
+
+Quick search:
+== Gyors keresés:
+
+Quit
+== Kilépés
+
+Quit anyway?
+== Mindenképpen kilép?
+
+REC %3d:%02d
+== REC %3d:%02d
+
+Reason:
+== Indok:
+
+Record demo
+== Demó felvétele
+
+Red team
+== Piros csapat
+
+Red team wins!
+== A piros csapat nyert!
+
+Refresh
+== Újratöltés
+
+Refreshing master servers
+== A master szerverek frissítése
+
+Remote console
+== Távoli konzol
+
+Remove
+== Eltávolítás
+
+Remove friend
+== Barát eltávolítása
+
+Rename
+== Átnevezés
+
+Rename demo
+== Demó átnevezése
+
+Reset filter
+== Szűrő visszaállítása
+
+Reset to defaults
+== Visszaállítás az alapértelmezettre
+
+Rifle
+== Lézer
+
+Round
+== Menet
+
+Sample rate
+== Mintavételi frekvencia
+
+Sat.
+== Sat.
+
+Score
+== Pontszám
+
+Score board
+== Pontszám tábla
+
+Score limit
+== Ponthatár
+
+Scoreboard
+== Pontszámtábla
+
+Screenshot
+== Pillanatkép
+
+Server address:
+== Szerver címe:
+
+Server details
+== Szerver részletei
+
+Server filter
+== Szerver szűrő
+
+Server info
+== Szerver infó
+
+Server not full
+== Szerver nincs tele
+
+Settings
+== Beállítások
+
+Shotgun
+== Sörétes puska
+
+Show chat
+== Chat mutatása
+
+Show friends only
+== Barátok mutatása
+
+Show ingame HUD
+== Játék közbeni HUD mutatása
+
+Show name plates
+== Név táblák mutatása
+
+Show only supported
+== Csak támogatott mutatása
+
+Size:
+== Méret:
+
+Skins
+== Skinek
+
+Sound
+== Hang
+
+Sound error
+== Hang hiba
+
+Sound volume
+== Hangerő
+
+Spectate
+== Megfigyelés
+
+Spectator mode
+== Néző mód
+
+Spectators
+== Megfigyelők
+
+Standard gametype
+== Általános játékfajta
+
+Standard map
+== Általános pálya
+
+Stop record
+== Felvétel megállítása
+
+Sudden Death
+== Gyors halál
+
+Switch weapon on pickup
+== Fegyverváltás felvételnél
+
+Team
+== Csapat
+
+Team chat
+== Csapat chat
+
+Teeworlds %s is out! Download it at www.teeworlds.com!
+== Teeworlds %s kint van! Töltsd le www.teeworlds.com oldalon!
+
+Texture Compression
+== Textúra tömörítés
+
+The audio device couldn't be initialised.
+== A hangeszköz nem kezdeményezhető.
+
+The server is running a non-standard tuning on a pure game type.
+== A szerver egy nem szabványos hangolást futtat a tiszta játék típuson.
+
+There's an unsaved map in the editor, you might want to save it before you quit the game.
+== Van egy mentett térkép a szerkesztőben, talán akarod menteni, mielőtt kilépsz a játékból.
+
+Time limit
+== Időhatár
+
+Time limit: %d min
+== Időhatár: %d perc
+
+Try again
+== Próbáld újra
+
+Type
+== Típus
+
+Type:
+== Típus:
+
+UI Color
+== UI Szín
+
+Unable to delete the demo
+== Nem sikerült törölni a demó
+
+Unable to rename the demo
+== Nem sikerült átnevezni a demó
+
+Use sounds
+== Hangok használata
+
+Use team colors for name plates
+== Csapat szín használata név tábláknál
+
+V-Sync
+== V-Sync
+
+Version
+== Verzió
+
+Version:
+== Verzió:
+
+Vote command:
+== Szavazás parancsa:
+
+Vote description:
+== Szavazás leírása:
+
+Vote no
+== Nem
+
+Vote yes
+== Igen
+
+Voting
+== Szavazás
+
+Warmup
+== Kezdés
+
+Weapon
+== Fegyver
+
+Welcome to Teeworlds
+== Üdvözöljük a Teeworlds-ben
+
+Yes
+== Igen
+
+You must restart the game for all settings to take effect.
+== Újra kell indítani a játékot, a beállítások érvénybe lépéséhez.
+
+Your skin
+== Te skined
+
+no limit
+== Nincs korlát
+
+##### needs translation #####
+
+Demofile: %s
+== 
+
+Play background music
+== 
+
+Player country:
+== 
+
+Spectate next
+== 
+
+Spectate previous
+== 
+
+Strict gametype filter
+== 
+
+##### old translations #####
+
diff --git a/data/languages/index.txt b/data/languages/index.txt
index 3a0c3a34..b7a6179b 100644
--- a/data/languages/index.txt
+++ b/data/languages/index.txt
@@ -1,6 +1,10 @@
 
 ##### language indices #####
 
+belarusian
+== Беларуская
+== 112
+
 bosnian
 == Bosanski
 == 70
@@ -37,6 +41,10 @@ german
 == Deutsch
 == 276
 
+hungarian
+== Magyar
+== 348
+
 italian
 == Italiano
 == 380
diff --git a/data/languages/ukrainian.txt b/data/languages/ukrainian.txt
index 9e696f51..93566829 100644
--- a/data/languages/ukrainian.txt
+++ b/data/languages/ukrainian.txt
@@ -50,7 +50,7 @@ Chat
 == Чат
 
 Close
-== Закрити
+== Зачинити
 
 Compatible version
 == Сумісна версія
@@ -86,10 +86,10 @@ Demos
 == Демо
 
 Disconnect
-== # Відключитись
+== Від'єднатись
 
 Disconnected
-== Відключено
+== Від'єднанно
 
 Display Modes
 == Режими дисплея
@@ -155,7 +155,7 @@ Game type
 == Тип гри
 
 Game types:
-== Тип гри:
+== Типи гри:
 
 General
 == Основні
@@ -164,7 +164,7 @@ Graphics
 == Графіка
 
 Grenade
-== Граната
+== Ракетниця
 
 Hammer
 == Молоток
@@ -215,7 +215,7 @@ Lht.
 == Яскравість
 
 Loading
-== Завантиження
+== Завантаження
 
 MOTD
 == MOTD
@@ -245,7 +245,7 @@ Mute when not active
 == Глушити звуки, коли гра неактивна
 
 Name
-== Імя
+== Ім'я
 
 News
 == Новини
@@ -332,7 +332,7 @@ Remote console
 == Консоль сервера
 
 Reset filter
-== Сикнути фільтри
+== Скинути фільтри
 
 Reset to defaults
 == Скинути налаштування
diff --git a/scripts/cmd5.py b/scripts/cmd5.py
index 07e35bb1..9b4804c7 100644
--- a/scripts/cmd5.py
+++ b/scripts/cmd5.py
@@ -30,6 +30,6 @@ for filename in sys.argv[1:]:
 
 hash = hashlib.md5(f).hexdigest().lower()[16:]
 #TODO 0.7: improve nethash creation
-if hash == "71de0f4d82688970":
+if hash == "63d6e69c6025feff":
 	hash = "626fce9a778df4d4"
 print('#define GAME_NETVERSION_HASH "%s"' % hash)
diff --git a/src/base/system.c b/src/base/system.c
index 551b3f1b..466e3ca6 100644
--- a/src/base/system.c
+++ b/src/base/system.c
@@ -42,10 +42,6 @@
 	#include <fcntl.h>
 	#include <direct.h>
 	#include <errno.h>
-
-	#ifndef EWOULDBLOCK
-		#define EWOULDBLOCK WSAEWOULDBLOCK
-	#endif
 #else
 	#error NOT IMPLEMENTED
 #endif
@@ -1102,30 +1098,31 @@ int net_set_blocking(NETSOCKET sock)
 
 int net_tcp_listen(NETSOCKET sock, int backlog)
 {
+	int err = -1;
 	if(sock.ipv4sock >= 0)
-		listen(sock.ipv4sock, backlog);
+		err = listen(sock.ipv4sock, backlog);
 	if(sock.ipv6sock >= 0)
-		listen(sock.ipv6sock, backlog);
-	return 0;
+		err = listen(sock.ipv6sock, backlog);
+	return err;
 }
 
 int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
 {
 	int s;
 	socklen_t sockaddr_len;
-	struct sockaddr addr;
 
 	*new_sock = invalid_socket;
 
-	sockaddr_len = sizeof(addr);
-
 	if(sock.ipv4sock >= 0)
 	{
-		s = accept(sock.ipv4sock, &addr, &sockaddr_len);
+		struct sockaddr_in addr;
+		sockaddr_len = sizeof(addr);
 
+		s = accept(sock.ipv4sock, (struct sockaddr *)&addr, &sockaddr_len);
+		
 		if (s != -1)
 		{
-			sockaddr_to_netaddr(&addr, a);
+			sockaddr_to_netaddr((const struct sockaddr *)&addr, a);
 			new_sock->type = NETTYPE_IPV4;
 			new_sock->ipv4sock = s;
 			return s;
@@ -1134,18 +1131,21 @@ int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *a)
 
 	if(sock.ipv6sock >= 0)
 	{
-		s = accept(sock.ipv6sock, &addr, &sockaddr_len);
+		struct sockaddr_in6 addr;
+		sockaddr_len = sizeof(addr);
 
+		s = accept(sock.ipv6sock, (struct sockaddr *)&addr, &sockaddr_len);
+		
 		if (s != -1)
 		{
-			sockaddr_to_netaddr(&addr, a);
+			sockaddr_to_netaddr((const struct sockaddr *)&addr, a);
 			new_sock->type = NETTYPE_IPV6;
 			new_sock->ipv6sock = s;
 			return s;
 		}
 	}
 
-	return 0;
+	return -1;
 }
 
 int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
@@ -1164,7 +1164,7 @@ int net_tcp_connect(NETSOCKET sock, const NETADDR *a)
 		return connect(sock.ipv6sock, (struct sockaddr *)&addr, sizeof(addr));
 	}
 
-	return 0;
+	return -1;
 }
 
 int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
@@ -1180,7 +1180,7 @@ int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr)
 
 int net_tcp_send(NETSOCKET sock, const void *data, int size)
 {
-	int bytes = 0;
+	int bytes = -1;
 
 	if(sock.ipv4sock >= 0)
 		bytes = send((int)sock.ipv4sock, (const char*)data, size, 0);
@@ -1192,7 +1192,7 @@ int net_tcp_send(NETSOCKET sock, const void *data, int size)
 
 int net_tcp_recv(NETSOCKET sock, void *data, int maxsize)
 {
-	int bytes = 0;
+	int bytes = -1;
 
 	if(sock.ipv4sock >= 0)
 		bytes = recv((int)sock.ipv4sock, (char*)data, maxsize, 0);
@@ -1209,12 +1209,20 @@ int net_tcp_close(NETSOCKET sock)
 
 int net_errno()
 {
+#if defined(CONF_FAMILY_WINDOWS)
+	return WSAGetLastError();
+#else
 	return errno;
+#endif
 }
 
 int net_would_block()
 {
+#if defined(CONF_FAMILY_WINDOWS)
+	return net_errno() == WSAEWOULDBLOCK;
+#else
 	return net_errno() == EWOULDBLOCK;
+#endif
 }
 
 int net_init()
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 90131703..bec7d4d6 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -1,5 +1,6 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <new>
 
 #include <stdlib.h> // qsort
 #include <stdarg.h>
@@ -26,6 +27,7 @@
 #include <engine/shared/compression.h>
 #include <engine/shared/datafile.h>
 #include <engine/shared/demo.h>
+#include <engine/shared/filecollection.h>
 #include <engine/shared/mapchecker.h>
 #include <engine/shared/network.h>
 #include <engine/shared/packer.h>
@@ -231,185 +233,6 @@ void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustD
 }
 
 
-bool CFileCollection::IsFilenameValid(const char *pFilename)
-{
-	if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength ||
-		str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) ||
-		str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt))
-		return false;
-
-	pFilename += m_FileDescLength;
-	if(pFilename[0] == '_' &&
-		pFilename[1] >= '0' && pFilename[1] <= '9' &&
-		pFilename[2] >= '0' && pFilename[2] <= '9' &&
-		pFilename[3] >= '0' && pFilename[3] <= '9' &&
-		pFilename[4] >= '0' && pFilename[4] <= '9' &&
-		pFilename[5] == '-' &&
-		pFilename[6] >= '0' && pFilename[6] <= '9' &&
-		pFilename[7] >= '0' && pFilename[7] <= '9' &&
-		pFilename[8] == '-' &&
-		pFilename[9] >= '0' && pFilename[9] <= '9' &&
-		pFilename[10] >= '0' && pFilename[10] <= '9' &&
-		pFilename[11] == '_' &&
-		pFilename[12] >= '0' && pFilename[12] <= '9' &&
-		pFilename[13] >= '0' && pFilename[13] <= '9' &&
-		pFilename[14] == '-' &&
-		pFilename[15] >= '0' && pFilename[15] <= '9' &&
-		pFilename[16] >= '0' && pFilename[16] <= '9' &&
-		pFilename[17] == '-' &&
-		pFilename[18] >= '0' && pFilename[18] <= '9' &&
-		pFilename[19] >= '0' && pFilename[19] <= '9')
-		return true;
-
-	return false;
-}
-
-int64 CFileCollection::ExtractTimestamp(const char *pTimestring)
-{
-	int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[1]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[2]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[3]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[5]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[6]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[8]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[9]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[11]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[12]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[14]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[15]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[17]-'0'; Timestamp <<= 4;
-	Timestamp += pTimestring[18]-'0';
-
-	return Timestamp;
-}
-
-void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring)
-{
-	pTimestring[19] = 0;
-	pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[16] = '-';
-	pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[13] = '-';
-	pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[10] = '_';
-	pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[7] = '-';
-	pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[4] = '-';
-	pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
-	pTimestring[0] = (Timestamp&0xF)+'0';
-}
-
-void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries)
-{
-	mem_zero(m_aTimestamps, sizeof(m_aTimestamps));
-	m_NumTimestamps = 0;
-	m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES));
-	str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc));
-	m_FileDescLength = str_length(m_aFileDesc);
-	str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt));
-	m_FileExtLength = str_length(m_aFileExt);
-	str_copy(m_aPath, pPath, sizeof(m_aPath));
-	m_pStorage = pStorage;
-
-	m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this);
-}
-
-void CFileCollection::AddEntry(int64 Timestamp)
-{
-	if(m_NumTimestamps == 0)
-	{
-		// empty list
-		m_aTimestamps[m_NumTimestamps++] = Timestamp;
-	}
-	else
-	{
-		// remove old file
-		if(m_NumTimestamps == m_MaxEntries)
-		{
-			char aBuf[512];
-			char aTimestring[TIMESTAMP_LENGTH];
-			BuildTimestring(m_aTimestamps[0], aTimestring);
-			str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt);
-			m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE);
-		}
-
-		// add entry to the sorted list
-		if(m_aTimestamps[0] > Timestamp)
-		{
-			// first entry
-			if(m_NumTimestamps < m_MaxEntries)
-			{
-				mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64));
-				m_aTimestamps[0] = Timestamp;
-				++m_NumTimestamps;
-			}
-		}
-		else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp)
-		{
-			// last entry
-			if(m_NumTimestamps == m_MaxEntries)
-			{
-				mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64));
-				m_aTimestamps[m_NumTimestamps-1] = Timestamp;
-			}
-			else
-				m_aTimestamps[m_NumTimestamps++] = Timestamp;
-		}
-		else
-		{
-			// middle entry
-			int Left = 0, Right = m_NumTimestamps-1;
-			while(Right-Left > 1)
-			{
-				int Mid = (Left+Right)/2;
-				if(m_aTimestamps[Mid] > Timestamp)
-					Right = Mid;
-				else
-					Left = Mid;
-			}
-
-			if(m_NumTimestamps == m_MaxEntries)
-			{
-				mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64));
-				m_aTimestamps[Right-1] = Timestamp;
-			}
-			else
-			{
-				mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64));
-				m_aTimestamps[Right] = Timestamp;
-				++m_NumTimestamps;
-			}
-		}
-	}
-}
-
-int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser)
-{
-	CFileCollection *pThis = static_cast<CFileCollection *>(pUser);
-
-	// check for valid file name format
-	if(IsDir || !pThis->IsFilenameValid(pFilename))
-		return 0;
-
-	// extract the timestamp
-	int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1);
-
-	// add the entry
-	pThis->AddEntry(Timestamp);
-
-	return 0;
-}
-
-
 CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta)
 {
 	m_pEditor = 0;
@@ -467,6 +290,7 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotD
 	m_aServerAddressStr[0] = 0;
 
 	mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
+	m_SnapshotStorage.Init();
 	m_RecivedSnapshots = 0;
 
 	m_VersionInfo.m_State = CVersionInfo::STATE_INIT;
@@ -2342,7 +2166,12 @@ void CClient::RegisterCommands()
 	m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this);
 }
 
-static CClient m_Client;
+static CClient *CreateClient()
+{
+	CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1));
+	mem_zero(pClient, sizeof(CClient));
+	return new(pClient) CClient;
+}
 
 /*
 	Server Time
@@ -2373,9 +2202,10 @@ int main(int argc, const char **argv) // ignore_convention
 	}
 #endif
 
+	CClient *pClient = CreateClient();
 	IKernel *pKernel = IKernel::Create();
-	pKernel->RegisterInterface(&m_Client);
-	m_Client.RegisterInterfaces();
+	pKernel->RegisterInterface(pClient);
+	pClient->RegisterInterfaces();
 
 	// create the components
 	IEngine *pEngine = CreateEngine("Teeworlds");
@@ -2428,12 +2258,12 @@ int main(int argc, const char **argv) // ignore_convention
 	pEngineMasterServer->Load();
 
 	// register all console commands
-	m_Client.RegisterCommands();
+	pClient->RegisterCommands();
 
 	pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
 
 	// init client's interfaces
-	m_Client.InitInterfaces();
+	pClient->InitInterfaces();
 
 	// execute config file
 	pConsole->ExecuteFile("settings.cfg");
@@ -2448,11 +2278,11 @@ int main(int argc, const char **argv) // ignore_convention
 	// restore empty config strings to their defaults
 	pConfig->RestoreStrings();
 
-	m_Client.Engine()->InitLogfile();
+	pClient->Engine()->InitLogfile();
 
 	// run the client
 	dbg_msg("client", "starting...");
-	m_Client.Run();
+	pClient->Run();
 
 	// write down the config and quit
 	pConfig->Save();
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index 3d81f073..1504a4e4 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -51,36 +51,6 @@ public:
 };
 
 
-class CFileCollection
-{
-	enum
-	{
-		MAX_ENTRIES=1000,
-		TIMESTAMP_LENGTH=20,	// _YYYY-MM-DD_HH-MM-SS
-	};
-
-	int64 m_aTimestamps[MAX_ENTRIES];
-	int m_NumTimestamps;
-	int m_MaxEntries;
-	char m_aFileDesc[128];
-	int m_FileDescLength;
-	char m_aFileExt[32];
-	int m_FileExtLength;
-	char m_aPath[512];
-	IStorage *m_pStorage;
-
-	bool IsFilenameValid(const char *pFilename);
-	int64 ExtractTimestamp(const char *pTimestring);
-	void BuildTimestring(int64 Timestamp, char *pTimestring);
-
-public:
-	void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries);
-	void AddEntry(int64 Timestamp);
-
-	static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser);
-};
-
-
 class CClient : public IClient, public CDemoPlayer::IListner
 {
 	// needed interfaces
diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp
index 99f82b50..eca39edb 100644
--- a/src/engine/client/friends.cpp
+++ b/src/engine/client/friends.cpp
@@ -12,6 +12,7 @@
 CFriends::CFriends()
 {
 	mem_zero(m_aFriends, sizeof(m_aFriends));
+	m_NumFriends = 0;
 }
 
 void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData)
diff --git a/src/engine/console.h b/src/engine/console.h
index 7c39cf49..0abf4ad2 100644
--- a/src/engine/console.h
+++ b/src/engine/console.h
@@ -21,8 +21,10 @@ public:
 		ACCESS_LEVEL_MOD,
 
 		TEMPCMD_NAME_LENGTH=32,
-		TEMPCMD_HELP_LENGTH=64,
+		TEMPCMD_HELP_LENGTH=96,
 		TEMPCMD_PARAMS_LENGTH=16,
+
+		MAX_PRINT_CB=4,
 	};
 
 	// TODO: rework this interface to reduce the amount of virtual calls
@@ -79,7 +81,8 @@ public:
 	virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0;
 	virtual void ExecuteFile(const char *pFilename) = 0;
 
-	virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) = 0;
+	virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0;
+	virtual void SetPrintOutputLevel(int Index, int OutputLevel) = 0;
 	virtual void Print(int Level, const char *pFrom, const char *pStr) = 0;
 
 	virtual void SetAccessLevel(int AccessLevel) = 0;
diff --git a/src/engine/server.h b/src/engine/server.h
index 28a97ecc..31134ca9 100644
--- a/src/engine/server.h
+++ b/src/engine/server.h
@@ -56,6 +56,8 @@ public:
 
 	virtual bool IsAuthed(int ClientID) = 0;
 	virtual void Kick(int ClientID, const char *pReason) = 0;
+
+	virtual void DemoRecorder_HandleAutoStart() = 0;
 };
 
 class IGameServer : public IInterface
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 2b174610..6f6d9e1c 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -16,6 +16,8 @@
 #include <engine/shared/config.h>
 #include <engine/shared/datafile.h>
 #include <engine/shared/demo.h>
+#include <engine/shared/econ.h>
+#include <engine/shared/filecollection.h>
 #include <engine/shared/mapchecker.h>
 #include <engine/shared/network.h>
 #include <engine/shared/packer.h>
@@ -654,9 +656,9 @@ void CServer::SendRconLineAuthed(const char *pLine, void *pUser)
 void CServer::SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID)
 {
 	CMsgPacker Msg(NETMSG_RCON_CMD_ADD);
-	Msg.AddString(pCommandInfo->m_pName, 32);
-	Msg.AddString(pCommandInfo->m_pHelp, 64);
-	Msg.AddString(pCommandInfo->m_pParams, 16);
+	Msg.AddString(pCommandInfo->m_pName, IConsole::TEMPCMD_NAME_LENGTH);
+	Msg.AddString(pCommandInfo->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH);
+	Msg.AddString(pCommandInfo->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH);
 	SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
 }
 
@@ -1084,6 +1086,8 @@ void CServer::PumpNetwork()
 		else
 			ProcessClientPacket(&Packet);
 	}
+
+	m_Econ.Update();
 }
 
 char *CServer::GetMapName()
@@ -1133,7 +1137,7 @@ int CServer::LoadMap(const char *pMapName)
 	str_copy(m_aCurrentMap, pMapName, sizeof(m_aCurrentMap));
 	//map_set(df);
 
-	// load compelate map into memory for download
+	// load complete map into memory for download
 	{
 		IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_READ, IStorage::TYPE_ALL);
 		m_CurrentMapSize = (int)io_length(File);
@@ -1158,7 +1162,7 @@ int CServer::Run()
 	m_pStorage = Kernel()->RequestInterface<IStorage>();
 
 	//
-	Console()->RegisterPrintCallback(SendRconLineAuthed, this);
+	m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, SendRconLineAuthed, this);
 
 	// load map
 	if(!LoadMap(g_Config.m_SvMap))
@@ -1181,7 +1185,6 @@ int CServer::Run()
 		BindAddr.port = g_Config.m_SvPort;
 	}
 
-
 	if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0))
 	{
 		dbg_msg("server", "couldn't open socket. port might already be in use");
@@ -1190,6 +1193,8 @@ int CServer::Run()
 
 	m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this);
 
+	m_Econ.Init(Console());
+
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName);
 	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
@@ -1327,6 +1332,8 @@ int CServer::Run()
 	{
 		if(m_aClients[i].m_State != CClient::STATE_EMPTY)
 			m_NetServer.Drop(i, "Server shutdown");
+
+		m_Econ.Shutdown();
 	}
 
 	GameServer()->OnShutdown();
@@ -1358,7 +1365,7 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser)
 	const char *pReason = "No reason given";
 
 	if(pResult->NumArguments() > 1)
-		Minutes = pResult->GetInteger(1);
+		Minutes = max(0, pResult->GetInteger(1));
 
 	if(pResult->NumArguments() > 2)
 		pReason = pResult->GetString(2);
@@ -1515,6 +1522,25 @@ void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser)
 	((CServer *)pUser)->m_RunServer = 0;
 }
 
+void CServer::DemoRecorder_HandleAutoStart()
+{
+	if(g_Config.m_SvAutoDemoRecord)
+	{
+		m_DemoRecorder.Stop();
+		char aFilename[128];
+		char aDate[20];
+		str_timestamp(aDate, sizeof(aDate));
+		str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate);
+		m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "server");
+		if(g_Config.m_SvAutoDemoMax)
+		{
+			// clean up auto recorded demos
+			CFileCollection AutoDemos;
+			AutoDemos.Init(Storage(), "demos/server", "autorecord", ".demo", g_Config.m_SvAutoDemoMax);
+		}
+	}
+}
+
 void CServer::ConRecord(IConsole::IResult *pResult, void *pUser)
 {
 	CServer* pServer = (CServer *)pUser;
@@ -1561,7 +1587,7 @@ void CServer::ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserDa
 	{
 		CServer *pThis = static_cast<CServer *>(pUserData);
 		const IConsole::CCommandInfo *pInfo = pThis->Console()->GetCommandInfo(pResult->GetString(0), CFGFLAG_SERVER, false);
-		int OldAccessLevel;
+		int OldAccessLevel = 0;
 		if(pInfo)
 			OldAccessLevel = pInfo->GetAccessLevel();
 		pfnCallback(pResult, pCallbackUserData);
@@ -1584,6 +1610,16 @@ void CServer::ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserDa
 		pfnCallback(pResult, pCallbackUserData);
 }
 
+void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CServer *pThis = static_cast<CServer *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
 void CServer::RegisterCommands()
 {
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
@@ -1605,6 +1641,7 @@ void CServer::RegisterCommands()
 
 	Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this);
 	Console()->Chain("mod_command", ConchainModCommandUpdate, this);
+	Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this);
 }
 
 
diff --git a/src/engine/server/server.h b/src/engine/server/server.h
index 5b6e038d..d8fdd8fa 100644
--- a/src/engine/server/server.h
+++ b/src/engine/server/server.h
@@ -113,6 +113,7 @@ public:
 	CSnapshotBuilder m_SnapshotBuilder;
 	CSnapIDPool m_IDPool;
 	CNetServer m_NetServer;
+	CEcon m_Econ;
 
 	IEngineMap *m_pMap;
 
@@ -122,6 +123,7 @@ public:
 	int m_MapReload;
 	int m_RconClientID;
 	int m_RconAuthLevel;
+	int m_PrintCBIndex;
 
 	int64 m_Lastheartbeat;
 	//static NETADDR4 master_server;
@@ -146,6 +148,8 @@ public:
 
 	void Kick(int ClientID, const char *pReason);
 
+	void DemoRecorder_HandleAutoStart();
+
 	//int Tick()
 	int64 TickStartTime(int Tick);
 	//int TickSpeed()
@@ -185,7 +189,6 @@ public:
 	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
 	int BanRemove(NETADDR Addr);
 
-
 	void PumpNetwork();
 
 	char *GetMapName();
@@ -206,6 +209,7 @@ public:
 	static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 	static void ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
+	static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 	void RegisterCommands();
 
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index 213ebf26..c812063a 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -86,6 +86,15 @@ MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remo
 MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER, "Remote console password for moderators (limited access)")
 MACRO_CONFIG_INT(SvRconMaxTries, sv_rcon_max_tries, 3, 0, 100, CFGFLAG_SERVER, "Maximum number of tries for remote console authentication")
 MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if remote console authentication fails. 0 makes it just use kick")
+MACRO_CONFIG_INT(SvAutoDemoRecord, sv_auto_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos")
+MACRO_CONFIG_INT(SvAutoDemoMax, sv_auto_demo_max, 10, 0, 1000, CFGFLAG_SERVER, "Maximum number of automatically recorded demos (0 = no limit)")
+
+MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_SERVER, "Address to bind the external console to. Anything but 'localhost' is dangerous")
+MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the external console")
+MACRO_CONFIG_STR(EcPassword, ec_password, 32, "", CFGFLAG_SERVER, "External console password")
+MACRO_CONFIG_INT(EcBantime, ec_bantime, 0, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if econ authentication fails. 0 just closes the connection")
+MACRO_CONFIG_INT(EcAuthTimeout, ec_auth_timeout, 30, 1, 120, CFGFLAG_SERVER, "Time in seconds before the the econ authentification times out")
+MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_SERVER, "Adjusts the amount of information in the external console")
 
 MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode")
 MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems")
diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp
index 847fb140..e4cb1991 100644
--- a/src/engine/shared/console.cpp
+++ b/src/engine/shared/console.cpp
@@ -173,20 +173,34 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
 	return Error;
 }
 
-void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData)
+int CConsole::RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData)
 {
-	m_pfnPrintCallback = pfnPrintCallback;
-	m_pPrintCallbackUserdata = pUserData;
+	if(m_NumPrintCB == MAX_PRINT_CB)
+		return -1;
+
+	m_aPrintCB[m_NumPrintCB].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG));
+	m_aPrintCB[m_NumPrintCB].m_pfnPrintCallback = pfnPrintCallback;
+	m_aPrintCB[m_NumPrintCB].m_pPrintCallbackUserdata = pUserData;
+	return m_NumPrintCB++;
+}
+
+void CConsole::SetPrintOutputLevel(int Index, int OutputLevel)
+{
+	if(Index >= 0 && Index < MAX_PRINT_CB)
+		m_aPrintCB[Index].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG));
 }
 
 void CConsole::Print(int Level, const char *pFrom, const char *pStr)
 {
 	dbg_msg(pFrom ,"%s", pStr);
-	if(Level <= g_Config.m_ConsoleOutputLevel && m_pfnPrintCallback)
+	for(int i = 0; i < m_NumPrintCB; ++i)
 	{
-		char aBuf[1024];
-		str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr);
-		m_pfnPrintCallback(aBuf, m_pPrintCallbackUserdata);
+		if(Level <= m_aPrintCB[i].m_OutputLevel && m_aPrintCB[i].m_pfnPrintCallback)
+		{
+			char aBuf[1024];
+			str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr);
+			m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata);
+		}
 	}
 }
 
@@ -562,8 +576,8 @@ CConsole::CConsole(int FlagMask)
 	m_ExecutionQueue.Reset();
 	m_pFirstCommand = 0;
 	m_pFirstExec = 0;
-	m_pPrintCallbackUserdata = 0;
-	m_pfnPrintCallback = 0;
+	mem_zero(m_aPrintCB, sizeof(m_aPrintCB));
+	m_NumPrintCB = 0;
 
 	m_pStorage = 0;
 
diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h
index b29f3202..6989c696 100644
--- a/src/engine/shared/console.h
+++ b/src/engine/shared/console.h
@@ -60,8 +60,13 @@ class CConsole : public IConsole
 	void ExecuteFileRecurse(const char *pFilename);
 	void ExecuteLineStroked(int Stroke, const char *pStr);
 
-	FPrintCallback m_pfnPrintCallback;
-	void *m_pPrintCallbackUserdata;
+	struct
+	{
+		int m_OutputLevel;
+		FPrintCallback m_pfnPrintCallback;
+		void *m_pPrintCallbackUserdata;
+	} m_aPrintCB[MAX_PRINT_CB];
+	int m_NumPrintCB;
 
 	enum
 	{
@@ -167,7 +172,8 @@ public:
 	virtual void ExecuteLine(const char *pStr);
 	virtual void ExecuteFile(const char *pFilename);
 
-	virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData);
+	virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData);
+	virtual void SetPrintOutputLevel(int Index, int OutputLevel);
 	virtual void Print(int Level, const char *pFrom, const char *pStr);
 
 	void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_MOD)); }
diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp
index 00410038..e2215635 100644
--- a/src/engine/shared/datafile.cpp
+++ b/src/engine/shared/datafile.cpp
@@ -422,6 +422,25 @@ unsigned CDataFileReader::Crc()
 	return m_pDataFile->m_Crc;
 }
 
+
+CDataFileWriter::CDataFileWriter()
+{
+	m_File = 0;
+	m_pItemTypes = static_cast<CItemTypeInfo *>(mem_alloc(sizeof(CItemTypeInfo) * MAX_ITEM_TYPES, 1));
+	m_pItems = static_cast<CItemInfo *>(mem_alloc(sizeof(CItemInfo) * MAX_ITEMS, 1));
+	m_pDatas = static_cast<CDataInfo *>(mem_alloc(sizeof(CDataInfo) * MAX_DATAS, 1));
+}
+
+CDataFileWriter::~CDataFileWriter()
+{
+	mem_free(m_pItemTypes);
+	m_pItemTypes = 0;
+	mem_free(m_pItems);
+	m_pItems = 0;
+	mem_free(m_pDatas);
+	m_pDatas = 0;
+}
+
 bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename)
 {
 	dbg_assert(!m_File, "a file already exists");
@@ -432,12 +451,12 @@ bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename)
 	m_NumItems = 0;
 	m_NumDatas = 0;
 	m_NumItemTypes = 0;
-	mem_zero(&m_aItemTypes, sizeof(m_aItemTypes));
+	mem_zero(m_pItemTypes, sizeof(CItemTypeInfo) * MAX_ITEM_TYPES);
 
-	for(int i = 0; i < 0xffff; i++)
+	for(int i = 0; i < MAX_ITEM_TYPES; i++)
 	{
-		m_aItemTypes[i].m_First = -1;
-		m_aItemTypes[i].m_Last = -1;
+		m_pItemTypes[i].m_First = -1;
+		m_pItemTypes[i].m_Last = -1;
 	}
 
 	return true;
@@ -451,29 +470,29 @@ int CDataFileWriter::AddItem(int Type, int ID, int Size, void *pData)
 	dbg_assert(m_NumItems < 1024, "too many items");
 	dbg_assert(Size%sizeof(int) == 0, "incorrect boundary");
 
-	m_aItems[m_NumItems].m_Type = Type;
-	m_aItems[m_NumItems].m_ID = ID;
-	m_aItems[m_NumItems].m_Size = Size;
+	m_pItems[m_NumItems].m_Type = Type;
+	m_pItems[m_NumItems].m_ID = ID;
+	m_pItems[m_NumItems].m_Size = Size;
 
 	// copy data
-	m_aItems[m_NumItems].m_pData = mem_alloc(Size, 1);
-	mem_copy(m_aItems[m_NumItems].m_pData, pData, Size);
+	m_pItems[m_NumItems].m_pData = mem_alloc(Size, 1);
+	mem_copy(m_pItems[m_NumItems].m_pData, pData, Size);
 
-	if(!m_aItemTypes[Type].m_Num) // count item types
+	if(!m_pItemTypes[Type].m_Num) // count item types
 		m_NumItemTypes++;
 
 	// link
-	m_aItems[m_NumItems].m_Prev = m_aItemTypes[Type].m_Last;
-	m_aItems[m_NumItems].m_Next = -1;
+	m_pItems[m_NumItems].m_Prev = m_pItemTypes[Type].m_Last;
+	m_pItems[m_NumItems].m_Next = -1;
 
-	if(m_aItemTypes[Type].m_Last != -1)
-		m_aItems[m_aItemTypes[Type].m_Last].m_Next = m_NumItems;
-	m_aItemTypes[Type].m_Last = m_NumItems;
+	if(m_pItemTypes[Type].m_Last != -1)
+		m_pItems[m_pItemTypes[Type].m_Last].m_Next = m_NumItems;
+	m_pItemTypes[Type].m_Last = m_NumItems;
 
-	if(m_aItemTypes[Type].m_First == -1)
-		m_aItemTypes[Type].m_First = m_NumItems;
+	if(m_pItemTypes[Type].m_First == -1)
+		m_pItemTypes[Type].m_First = m_NumItems;
 
-	m_aItemTypes[Type].m_Num++;
+	m_pItemTypes[Type].m_Num++;
 
 	m_NumItems++;
 	return m_NumItems-1;
@@ -485,7 +504,7 @@ int CDataFileWriter::AddData(int Size, void *pData)
 
 	dbg_assert(m_NumDatas < 1024, "too much data");
 
-	CDataInfo *pInfo = &m_aDatas[m_NumDatas];
+	CDataInfo *pInfo = &m_pDatas[m_NumDatas];
 	unsigned long s = compressBound(Size);
 	void *pCompData = mem_alloc(s, 1); // temporary buffer that we use during compression
 
@@ -540,13 +559,13 @@ int CDataFileWriter::Finish()
 	for(int i = 0; i < m_NumItems; i++)
 	{
 		if(DEBUG)
-			dbg_msg("datafile", "item=%d size=%d (%d)", i, m_aItems[i].m_Size, m_aItems[i].m_Size+sizeof(CDatafileItem));
-		ItemSize += m_aItems[i].m_Size + sizeof(CDatafileItem);
+			dbg_msg("datafile", "item=%d size=%d (%d)", i, m_pItems[i].m_Size, m_pItems[i].m_Size+sizeof(CDatafileItem));
+		ItemSize += m_pItems[i].m_Size + sizeof(CDatafileItem);
 	}
 
 
 	for(int i = 0; i < m_NumDatas; i++)
-		DataSize += m_aDatas[i].m_CompressedSize;
+		DataSize += m_pDatas[i].m_CompressedSize;
 
 	// calculate the complete size
 	TypesSize = m_NumItemTypes*sizeof(CDatafileItemType);
@@ -587,30 +606,30 @@ int CDataFileWriter::Finish()
 	// write types
 	for(int i = 0, Count = 0; i < 0xffff; i++)
 	{
-		if(m_aItemTypes[i].m_Num)
+		if(m_pItemTypes[i].m_Num)
 		{
 			// write info
 			CDatafileItemType Info;
 			Info.m_Type = i;
 			Info.m_Start = Count;
-			Info.m_Num = m_aItemTypes[i].m_Num;
+			Info.m_Num = m_pItemTypes[i].m_Num;
 			if(DEBUG)
 				dbg_msg("datafile", "writing type=%x start=%d num=%d", Info.m_Type, Info.m_Start, Info.m_Num);
 #if defined(CONF_ARCH_ENDIAN_BIG)
 			swap_endian(&Info, sizeof(int), sizeof(CDatafileItemType)/sizeof(int));
 #endif
 			io_write(m_File, &Info, sizeof(Info));
-			Count += m_aItemTypes[i].m_Num;
+			Count += m_pItemTypes[i].m_Num;
 		}
 	}
 
 	// write item offsets
 	for(int i = 0, Offset = 0; i < 0xffff; i++)
 	{
-		if(m_aItemTypes[i].m_Num)
+		if(m_pItemTypes[i].m_Num)
 		{
-			// write all m_aItems in of this type
-			int k = m_aItemTypes[i].m_First;
+			// write all m_pItems in of this type
+			int k = m_pItemTypes[i].m_First;
 			while(k != -1)
 			{
 				if(DEBUG)
@@ -620,10 +639,10 @@ int CDataFileWriter::Finish()
 				swap_endian(&Temp, sizeof(int), sizeof(Temp)/sizeof(int));
 #endif
 				io_write(m_File, &Temp, sizeof(Temp));
-				Offset += m_aItems[k].m_Size + sizeof(CDatafileItem);
+				Offset += m_pItems[k].m_Size + sizeof(CDatafileItem);
 
 				// next
-				k = m_aItems[k].m_Next;
+				k = m_pItems[k].m_Next;
 			}
 		}
 	}
@@ -638,45 +657,45 @@ int CDataFileWriter::Finish()
 		swap_endian(&Temp, sizeof(int), sizeof(Temp)/sizeof(int));
 #endif
 		io_write(m_File, &Temp, sizeof(Temp));
-		Offset += m_aDatas[i].m_CompressedSize;
+		Offset += m_pDatas[i].m_CompressedSize;
 	}
 
 	// write data uncompressed sizes
 	for(int i = 0; i < m_NumDatas; i++)
 	{
 		if(DEBUG)
-			dbg_msg("datafile", "writing data uncompressed size num=%d size=%d", i, m_aDatas[i].m_UncompressedSize);
-		int UncompressedSize = m_aDatas[i].m_UncompressedSize;
+			dbg_msg("datafile", "writing data uncompressed size num=%d size=%d", i, m_pDatas[i].m_UncompressedSize);
+		int UncompressedSize = m_pDatas[i].m_UncompressedSize;
 #if defined(CONF_ARCH_ENDIAN_BIG)
 		swap_endian(&UncompressedSize, sizeof(int), sizeof(UncompressedSize)/sizeof(int));
 #endif
 		io_write(m_File, &UncompressedSize, sizeof(UncompressedSize));
 	}
 
-	// write m_aItems
+	// write m_pItems
 	for(int i = 0; i < 0xffff; i++)
 	{
-		if(m_aItemTypes[i].m_Num)
+		if(m_pItemTypes[i].m_Num)
 		{
-			// write all m_aItems in of this type
-			int k = m_aItemTypes[i].m_First;
+			// write all m_pItems in of this type
+			int k = m_pItemTypes[i].m_First;
 			while(k != -1)
 			{
 				CDatafileItem Item;
-				Item.m_TypeAndID = (i<<16)|m_aItems[k].m_ID;
-				Item.m_Size = m_aItems[k].m_Size;
+				Item.m_TypeAndID = (i<<16)|m_pItems[k].m_ID;
+				Item.m_Size = m_pItems[k].m_Size;
 				if(DEBUG)
-					dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_aItems[k].m_ID, m_aItems[k].m_Size);
+					dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_pItems[k].m_ID, m_pItems[k].m_Size);
 
 #if defined(CONF_ARCH_ENDIAN_BIG)
 				swap_endian(&Item, sizeof(int), sizeof(Item)/sizeof(int));
-				swap_endian(m_aItems[k].m_pData, sizeof(int), m_aItems[k].m_Size/sizeof(int));
+				swap_endian(m_pItems[k].m_pData, sizeof(int), m_pItems[k].m_Size/sizeof(int));
 #endif
 				io_write(m_File, &Item, sizeof(Item));
-				io_write(m_File, m_aItems[k].m_pData, m_aItems[k].m_Size);
+				io_write(m_File, m_pItems[k].m_pData, m_pItems[k].m_Size);
 
 				// next
-				k = m_aItems[k].m_Next;
+				k = m_pItems[k].m_Next;
 			}
 		}
 	}
@@ -685,15 +704,15 @@ int CDataFileWriter::Finish()
 	for(int i = 0; i < m_NumDatas; i++)
 	{
 		if(DEBUG)
-			dbg_msg("datafile", "writing data id=%d size=%d", i, m_aDatas[i].m_CompressedSize);
-		io_write(m_File, m_aDatas[i].m_pCompressedData, m_aDatas[i].m_CompressedSize);
+			dbg_msg("datafile", "writing data id=%d size=%d", i, m_pDatas[i].m_CompressedSize);
+		io_write(m_File, m_pDatas[i].m_pCompressedData, m_pDatas[i].m_CompressedSize);
 	}
 
 	// free data
 	for(int i = 0; i < m_NumItems; i++)
-		mem_free(m_aItems[i].m_pData);
+		mem_free(m_pItems[i].m_pData);
 	for(int i = 0; i < m_NumDatas; ++i)
-		mem_free(m_aDatas[i].m_pCompressedData);
+		mem_free(m_pDatas[i].m_pCompressedData);
 
 	io_close(m_File);
 	m_File = 0;
diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h
index 9f27f968..cafce20e 100644
--- a/src/engine/shared/datafile.h
+++ b/src/engine/shared/datafile.h
@@ -61,16 +61,24 @@ class CDataFileWriter
 		int m_Last;
 	};
 
+	enum
+	{
+		MAX_ITEM_TYPES=0xffff,
+		MAX_ITEMS=1024,
+		MAX_DATAS=1024,
+	};
+
 	IOHANDLE m_File;
 	int m_NumItems;
 	int m_NumDatas;
 	int m_NumItemTypes;
-	CItemTypeInfo m_aItemTypes[0xffff];
-	CItemInfo m_aItems[1024];
-	CDataInfo m_aDatas[1024];
+	CItemTypeInfo *m_pItemTypes;
+	CItemInfo *m_pItems;
+	CDataInfo *m_pDatas;
 
 public:
-	CDataFileWriter() : m_File(0) {}
+	CDataFileWriter();
+	~CDataFileWriter();
 	bool Open(class IStorage *pStorage, const char *Filename);
 	int AddData(int Size, void *pData);
 	int AddDataSwapped(int Size, void *pData);
diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp
new file mode 100644
index 00000000..617cdbd6
--- /dev/null
+++ b/src/engine/shared/econ.cpp
@@ -0,0 +1,173 @@
+#include <engine/console.h>
+#include <engine/shared/config.h>
+
+#include "econ.h"
+
+int CEcon::NewClientCallback(int ClientID, void *pUser)
+{
+	CEcon *pThis = (CEcon *)pUser;
+
+	NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID);
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	char aBuf[128];
+	str_format(aBuf, sizeof(aBuf), "client accepted. cid=%d addr=%s'", ClientID, aAddrStr);
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf);
+
+	pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED;
+	pThis->m_aClients[ClientID].m_TimeConnected = time_get();
+	pThis->m_aClients[ClientID].m_AuthTries = 0;
+
+	pThis->m_NetConsole.Send(ClientID, "Enter password:");
+	return 0;
+}
+
+int CEcon::DelClientCallback(int ClientID, const char *pReason, void *pUser)
+{
+	CEcon *pThis = (CEcon *)pUser;
+
+	NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID);
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	char aBuf[256];
+	str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason);
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf);
+
+	pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY;
+	return 0;
+}
+
+void CEcon::SendLineCB(const char *pLine, void *pUserData)
+{
+	static_cast<CEcon *>(pUserData)->Send(-1, pLine);
+}
+
+void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CEcon *pThis = static_cast<CEcon *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
+void CEcon::Init(IConsole *pConsole)
+{
+	m_pConsole = pConsole;
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		m_aClients[i].m_State = CClient::STATE_EMPTY;
+
+	m_Ready = false;
+
+	if(g_Config.m_EcPort == 0 || g_Config.m_EcPassword[0] == 0)
+		return;
+
+	NETADDR BindAddr;
+	if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0)
+		BindAddr.port = g_Config.m_EcPort;
+	else
+	{
+		mem_zero(&BindAddr, sizeof(BindAddr));
+		BindAddr.type = NETTYPE_ALL;
+		BindAddr.port = g_Config.m_EcPort;
+	}
+
+	if(m_NetConsole.Open(BindAddr, 0))
+	{
+		m_NetConsole.SetCallbacks(NewClientCallback, DelClientCallback, this);
+		m_Ready = true;
+		char aBuf[128];
+		str_format(aBuf, sizeof(aBuf), "bound to %s:%d", g_Config.m_EcBindaddr, g_Config.m_EcPort);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", aBuf);
+
+		Console()->Chain("ec_output_level", ConchainEconOutputLevelUpdate, this);
+		m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_EcOutputLevel, SendLineCB, this);
+	}
+	else
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", "couldn't open socket. port might already be in use");
+}
+
+void CEcon::Update()
+{
+	if(!m_Ready)
+		return;
+
+	m_NetConsole.Update();
+
+	char aBuf[NET_MAX_PACKETSIZE];
+	int ClientID;
+
+	while(m_NetConsole.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID))
+	{
+		dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "got message from empty slot");
+		if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED)
+		{
+			if(str_comp(aBuf, g_Config.m_EcPassword) == 0)
+			{
+				m_aClients[ClientID].m_State = CClient::STATE_AUTHED;
+				m_NetConsole.Send(ClientID, "Authentication successful. External console access granted.");
+
+				str_format(aBuf, sizeof(aBuf), "cid=%d authed", ClientID);
+				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf);
+			}
+			else
+			{
+				m_aClients[ClientID].m_AuthTries++;
+				char aBuf[128];
+				str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, MAX_AUTH_TRIES);
+				m_NetConsole.Send(ClientID, aBuf);
+				if(m_aClients[ClientID].m_AuthTries >= MAX_AUTH_TRIES)
+				{
+					if(!g_Config.m_EcBantime)
+						m_NetConsole.Drop(ClientID, "Too many authentication tries");
+					else
+					{
+						NETADDR Addr = m_NetConsole.ClientAddr(ClientID);
+						m_NetConsole.AddBan(Addr, g_Config.m_EcBantime*60);
+					}
+				}
+			}
+		}
+		else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		{
+			char aFormatted[256];
+			str_format(aFormatted, sizeof(aBuf), "cid=%d cmd='%s'", ClientID, aBuf);
+			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted);
+			Console()->ExecuteLine(aBuf);
+		}
+	}
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; ++i)
+	{
+		if(m_aClients[i].m_State == CClient::STATE_CONNECTED &&
+			time_get() > m_aClients[i].m_TimeConnected + g_Config.m_EcAuthTimeout * time_freq())
+			m_NetConsole.Drop(i, "authentication timeout");
+	}
+}
+
+void CEcon::Send(int ClientID, const char *pLine)
+{
+	if(!m_Ready)
+		return;
+
+	if(ClientID == -1)
+	{
+		for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		{
+			if(m_aClients[i].m_State == CClient::STATE_AUTHED)
+				m_NetConsole.Send(i, pLine);
+		}
+	}
+	else if(ClientID >= 0 && ClientID < NET_MAX_CONSOLE_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		m_NetConsole.Send(ClientID, pLine);
+}
+
+void CEcon::Shutdown()
+{
+	if(!m_Ready)
+		return;
+
+	m_NetConsole.Close();
+}
diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h
new file mode 100644
index 00000000..daec34c4
--- /dev/null
+++ b/src/engine/shared/econ.h
@@ -0,0 +1,50 @@
+#ifndef ENGINE_SHARED_ECON_H
+#define ENGINE_SHARED_ECON_H
+
+#include "network.h"
+
+class CEcon
+{
+	enum
+	{
+		MAX_AUTH_TRIES=3,
+	};
+
+	class CClient
+	{
+	public:
+		enum
+		{
+			STATE_EMPTY=0,
+			STATE_CONNECTED,
+			STATE_AUTHED,
+		};
+
+		int m_State;
+		int64 m_TimeConnected;
+		int m_AuthTries;
+	};
+	CClient m_aClients[NET_MAX_CONSOLE_CLIENTS];
+
+	IConsole *m_pConsole;
+	CNetConsole m_NetConsole;
+
+	bool m_Ready;
+	int m_PrintCBIndex;
+
+	static void SendLineCB(const char *pLine, void *pUserData);
+	static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
+
+	static int NewClientCallback(int ClientID, void *pUser);
+	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
+
+public:
+	IConsole *Console() { return m_pConsole; }
+
+	void Init(IConsole *pConsole);
+	void Update();
+	void Send(int ClientID, const char *pLine);
+	void Shutdown();
+};
+
+#endif
diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp
new file mode 100644
index 00000000..622534f2
--- /dev/null
+++ b/src/engine/shared/filecollection.cpp
@@ -0,0 +1,186 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+
+#include <base/math.h>
+
+#include <engine/storage.h>
+
+#include "filecollection.h"
+
+bool CFileCollection::IsFilenameValid(const char *pFilename)
+{
+	if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength ||
+		str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) ||
+		str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt))
+		return false;
+
+	pFilename += m_FileDescLength;
+	if(pFilename[0] == '_' &&
+		pFilename[1] >= '0' && pFilename[1] <= '9' &&
+		pFilename[2] >= '0' && pFilename[2] <= '9' &&
+		pFilename[3] >= '0' && pFilename[3] <= '9' &&
+		pFilename[4] >= '0' && pFilename[4] <= '9' &&
+		pFilename[5] == '-' &&
+		pFilename[6] >= '0' && pFilename[6] <= '9' &&
+		pFilename[7] >= '0' && pFilename[7] <= '9' &&
+		pFilename[8] == '-' &&
+		pFilename[9] >= '0' && pFilename[9] <= '9' &&
+		pFilename[10] >= '0' && pFilename[10] <= '9' &&
+		pFilename[11] == '_' &&
+		pFilename[12] >= '0' && pFilename[12] <= '9' &&
+		pFilename[13] >= '0' && pFilename[13] <= '9' &&
+		pFilename[14] == '-' &&
+		pFilename[15] >= '0' && pFilename[15] <= '9' &&
+		pFilename[16] >= '0' && pFilename[16] <= '9' &&
+		pFilename[17] == '-' &&
+		pFilename[18] >= '0' && pFilename[18] <= '9' &&
+		pFilename[19] >= '0' && pFilename[19] <= '9')
+		return true;
+
+	return false;
+}
+
+int64 CFileCollection::ExtractTimestamp(const char *pTimestring)
+{
+	int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[1]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[2]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[3]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[5]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[6]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[8]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[9]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[11]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[12]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[14]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[15]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[17]-'0'; Timestamp <<= 4;
+	Timestamp += pTimestring[18]-'0';
+
+	return Timestamp;
+}
+
+void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring)
+{
+	pTimestring[19] = 0;
+	pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[16] = '-';
+	pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[13] = '-';
+	pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[10] = '_';
+	pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[7] = '-';
+	pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[4] = '-';
+	pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4;
+	pTimestring[0] = (Timestamp&0xF)+'0';
+}
+
+void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries)
+{
+	mem_zero(m_aTimestamps, sizeof(m_aTimestamps));
+	m_NumTimestamps = 0;
+	m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES));
+	str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc));
+	m_FileDescLength = str_length(m_aFileDesc);
+	str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt));
+	m_FileExtLength = str_length(m_aFileExt);
+	str_copy(m_aPath, pPath, sizeof(m_aPath));
+	m_pStorage = pStorage;
+
+	m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this);
+}
+
+void CFileCollection::AddEntry(int64 Timestamp)
+{
+	if(m_NumTimestamps == 0)
+	{
+		// empty list
+		m_aTimestamps[m_NumTimestamps++] = Timestamp;
+	}
+	else
+	{
+		// remove old file
+		if(m_NumTimestamps == m_MaxEntries)
+		{
+			char aBuf[512];
+			char aTimestring[TIMESTAMP_LENGTH];
+			BuildTimestring(m_aTimestamps[0], aTimestring);
+			str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt);
+			m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE);
+		}
+
+		// add entry to the sorted list
+		if(m_aTimestamps[0] > Timestamp)
+		{
+			// first entry
+			if(m_NumTimestamps < m_MaxEntries)
+			{
+				mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64));
+				m_aTimestamps[0] = Timestamp;
+				++m_NumTimestamps;
+			}
+		}
+		else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp)
+		{
+			// last entry
+			if(m_NumTimestamps == m_MaxEntries)
+			{
+				mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64));
+				m_aTimestamps[m_NumTimestamps-1] = Timestamp;
+			}
+			else
+				m_aTimestamps[m_NumTimestamps++] = Timestamp;
+		}
+		else
+		{
+			// middle entry
+			int Left = 0, Right = m_NumTimestamps-1;
+			while(Right-Left > 1)
+			{
+				int Mid = (Left+Right)/2;
+				if(m_aTimestamps[Mid] > Timestamp)
+					Right = Mid;
+				else
+					Left = Mid;
+			}
+
+			if(m_NumTimestamps == m_MaxEntries)
+			{
+				mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64));
+				m_aTimestamps[Right-1] = Timestamp;
+			}
+			else
+			{
+				mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64));
+				m_aTimestamps[Right] = Timestamp;
+				++m_NumTimestamps;
+			}
+		}
+	}
+}
+
+int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser)
+{
+	CFileCollection *pThis = static_cast<CFileCollection *>(pUser);
+
+	// check for valid file name format
+	if(IsDir || !pThis->IsFilenameValid(pFilename))
+		return 0;
+
+	// extract the timestamp
+	int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1);
+
+	// add the entry
+	pThis->AddEntry(Timestamp);
+
+	return 0;
+}
diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h
new file mode 100644
index 00000000..ac633892
--- /dev/null
+++ b/src/engine/shared/filecollection.h
@@ -0,0 +1,35 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#ifndef ENGINE_SHARED_FILECOLLECTION_H
+#define ENGINE_SHARED_FILECOLLECTION_H
+
+class CFileCollection
+{
+	enum
+	{
+		MAX_ENTRIES=1000,
+		TIMESTAMP_LENGTH=20,	// _YYYY-MM-DD_HH-MM-SS
+	};
+
+	int64 m_aTimestamps[MAX_ENTRIES];
+	int m_NumTimestamps;
+	int m_MaxEntries;
+	char m_aFileDesc[128];
+	int m_FileDescLength;
+	char m_aFileExt[32];
+	int m_FileExtLength;
+	char m_aPath[512];
+	IStorage *m_pStorage;
+
+	bool IsFilenameValid(const char *pFilename);
+	int64 ExtractTimestamp(const char *pTimestring);
+	void BuildTimestring(int64 Timestamp, char *pTimestring);
+
+public:
+	void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries);
+	void AddEntry(int64 Timestamp);
+
+	static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser);
+};
+
+#endif
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index 228ba6dd..d10c03b6 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -49,6 +49,7 @@ enum
 	NET_MAX_CHUNKHEADERSIZE = 5,
 	NET_PACKETHEADERSIZE = 3,
 	NET_MAX_CLIENTS = 16,
+	NET_MAX_CONSOLE_CLIENTS = 4,
 	NET_MAX_SEQUENCE = 1<<10,
 	NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1,
 
@@ -192,6 +193,36 @@ public:
 	int AckSequence() const { return m_Ack; }
 };
 
+class CConsoleNetConnection
+{
+private:
+	int m_State;
+
+	NETADDR m_PeerAddr;
+	NETSOCKET m_Socket;
+
+	char m_aBuffer[NET_MAX_PACKETSIZE];
+	int m_BufferOffset;
+
+	char m_aErrorString[256];
+
+	bool m_LineEndingDetected;
+	char m_aLineEnding[3];
+
+public:
+	void Init(NETSOCKET Socket, const NETADDR *pAddr);
+	void Disconnect(const char *pReason);
+
+	int State() const { return m_State; }
+	NETADDR PeerAddress() const { return m_PeerAddr; }
+	const char *ErrorString() const { return m_aErrorString; }
+
+	void Reset();
+	int Update();
+	int Send(const char *pLine);
+	int Recv(char *pLine, int MaxLength);
+};
+
 class CNetRecvUnpacker
 {
 public:
@@ -292,6 +323,59 @@ public:
 	void SetMaxClientsPerIP(int Max);
 };
 
+class CNetConsole
+{
+	enum
+	{
+		MAX_BANS=128,
+	};
+
+	int FindBan(NETADDR Addr);
+	void UpdateBans();
+
+	struct CBanEntry
+	{
+		NETADDR m_Addr;
+		int m_Expires;
+	} m_aBans[MAX_BANS];
+	int m_NumBans;
+
+	struct CSlot
+	{
+		CConsoleNetConnection m_Connection;
+	};
+
+	NETSOCKET m_Socket;
+	CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS];
+
+	NETFUNC_NEWCLIENT m_pfnNewClient;
+	NETFUNC_DELCLIENT m_pfnDelClient;
+	void *m_UserPtr;
+
+	CNetRecvUnpacker m_RecvUnpacker;
+
+public:
+	void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
+
+	//
+	bool Open(NETADDR BindAddr, int Flags);
+	int Close();
+
+	//
+	int Recv(char *pLine, int MaxLength, int *pClientID = 0);
+	int Send(int ClientID, const char *pLine);
+	int Update();
+
+	//
+	int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
+	int Drop(int ClientID, const char *pReason);
+
+	bool AddBan(NETADDR Addr, int Seconds);
+
+	// status requests
+	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+};
+
 
 
 // client side
diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp
new file mode 100644
index 00000000..13ed3751
--- /dev/null
+++ b/src/engine/shared/network_console.cpp
@@ -0,0 +1,219 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <base/system.h>
+#include "network.h"
+
+bool CNetConsole::Open(NETADDR BindAddr, int Flags)
+{
+	// zero out the whole structure
+	mem_zero(this, sizeof(*this));
+	m_Socket.type = NETTYPE_INVALID;
+	m_Socket.ipv4sock = -1;
+	m_Socket.ipv6sock = -1;
+
+	// open socket
+	m_Socket = net_tcp_create(BindAddr);
+	if(!m_Socket.type)
+		return false;
+	if(net_tcp_listen(m_Socket, NET_MAX_CONSOLE_CLIENTS))
+		return false;
+	net_set_non_blocking(m_Socket);
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Reset();
+
+	return true;
+}
+
+void CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+{
+	m_pfnNewClient = pfnNewClient;
+	m_pfnDelClient = pfnDelClient;
+	m_UserPtr = pUser;
+}
+
+int CNetConsole::Close()
+{
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Disconnect("closing console");
+
+	net_tcp_close(m_Socket);
+
+	return 0;
+}
+
+int CNetConsole::Drop(int ClientID, const char *pReason)
+{
+	if(m_pfnDelClient)
+		m_pfnDelClient(ClientID, pReason, m_UserPtr);
+
+	m_aSlots[ClientID].m_Connection.Disconnect(pReason);
+
+	return 0;
+}
+
+int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
+{
+	char aError[256] = { 0 };
+	int FreeSlot = -1;
+	
+	// look for free slot or multiple client
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
+			FreeSlot = i;
+		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
+		{
+			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
+			if(net_addr_comp(pAddr, &PeerAddr) == 0)
+			{
+				str_copy(aError, "only one client per IP allowed", sizeof(aError));
+				break;
+			}
+		}
+	}
+
+	// accept client
+	if(!aError[0] && FreeSlot != -1)
+	{
+		m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr);
+		if(m_pfnNewClient)
+			m_pfnNewClient(FreeSlot, m_UserPtr);
+		return 0;
+	}
+
+	// reject client
+	if(!aError[0])
+		str_copy(aError, "no free slot available", sizeof(aError));
+
+	net_tcp_send(Socket, aError, str_length(aError));
+	net_tcp_close(Socket);
+
+	return -1;
+}
+
+int CNetConsole::Update()
+{
+	NETSOCKET Socket;
+	NETADDR Addr;
+
+	if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
+	{
+		int Index = FindBan(Addr);
+		if(Index == -1)
+			AcceptClient(Socket, &Addr);
+		else
+		{
+			char aBuf[128];
+			if(m_aBans[Index].m_Expires > -1)
+			{
+				int Mins = (m_aBans[Index].m_Expires-time_timestamp()+ 59) / 60;
+				if(Mins <= 1)
+					str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute");
+				else
+					str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins);
+			}
+			else
+				str_format(aBuf, sizeof(aBuf), "You have been banned for life");
+			
+			net_tcp_send(Socket, aBuf, str_length(aBuf));
+			net_tcp_close(Socket);
+		}
+	}
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
+			m_aSlots[i].m_Connection.Update();
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
+			Drop(i, m_aSlots[i].m_Connection.ErrorString());
+	}
+
+	UpdateBans();
+
+	return 0;
+}
+
+int CNetConsole::Recv(char *pLine, int MaxLength, int *pClientID)
+{
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength))
+		{
+			if(pClientID)
+				*pClientID = i;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CNetConsole::Send(int ClientID, const char *pLine)
+{
+	if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE)
+		return m_aSlots[ClientID].m_Connection.Send(pLine);
+	else
+		return -1;
+}
+
+int CNetConsole::FindBan(NETADDR Addr)
+{
+	Addr.port = 0;
+	for(int i = 0; i < m_NumBans; i++)
+		if(net_addr_comp(&m_aBans[i].m_Addr, &Addr) == 0)
+			return i;
+
+	return -1;
+}
+
+bool CNetConsole::AddBan(NETADDR Addr, int Seconds)
+{
+	if(m_NumBans == MAX_BANS)
+		return false;
+	
+	Addr.port = 0;
+	int Index = FindBan(Addr);
+	if(Index == -1)
+	{
+		Index = m_NumBans++;
+		m_aBans[Index].m_Addr = Addr;
+	}
+	m_aBans[Index].m_Expires = Seconds>0 ? time_timestamp()+Seconds : -1;
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
+		{
+			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
+			PeerAddr.port = 0;
+			if(net_addr_comp(&Addr, &PeerAddr) == 0)
+			{
+				char aBuf[128];
+				if(Seconds>0)
+				{
+					int Mins = (Seconds + 59) / 60;
+					if(Mins <= 1)
+						str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute");
+					else
+						str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins);
+				}
+				else
+					str_format(aBuf, sizeof(aBuf), "You have been banned for life");
+				Drop(i, aBuf);
+			}
+		}
+	}
+	return true;
+}
+
+void CNetConsole::UpdateBans()
+{
+	int Now = time_timestamp();
+	for(int i = 0; i < m_NumBans; ++i)
+		if(m_aBans[i].m_Expires > 0 && m_aBans[i].m_Expires < Now)
+		{
+			m_aBans[i] = m_aBans[m_NumBans-1];
+			--m_NumBans;
+			break;
+		}
+}
diff --git a/src/engine/shared/network_console_conn.cpp b/src/engine/shared/network_console_conn.cpp
new file mode 100644
index 00000000..75b581fa
--- /dev/null
+++ b/src/engine/shared/network_console_conn.cpp
@@ -0,0 +1,186 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <base/system.h>
+#include "network.h"
+
+void CConsoleNetConnection::Reset()
+{
+	m_State = NET_CONNSTATE_OFFLINE;
+	mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
+	m_aErrorString[0] = 0;
+
+	m_Socket.type = NETTYPE_INVALID;
+	m_Socket.ipv4sock = -1;
+	m_Socket.ipv6sock = -1;
+	m_aBuffer[0] = 0;
+	m_BufferOffset = 0;
+
+	m_LineEndingDetected = false;
+	#if defined(CONF_FAMILY_WINDOWS)
+		m_aLineEnding[0] = '\r';
+		m_aLineEnding[1] = '\n';
+		m_aLineEnding[2] = 0;
+	#else
+		m_aLineEnding[0] = '\n';
+		m_aLineEnding[1] = 0;
+		m_aLineEnding[2] = 0;
+	#endif
+}
+
+void CConsoleNetConnection::Init(NETSOCKET Socket, const NETADDR *pAddr)
+{
+	Reset();
+
+	m_Socket = Socket;
+	net_set_non_blocking(m_Socket);
+
+	m_PeerAddr = *pAddr;
+	m_State = NET_CONNSTATE_ONLINE;
+}
+
+void CConsoleNetConnection::Disconnect(const char *pReason)
+{
+	if(State() == NET_CONNSTATE_OFFLINE)
+		return;
+
+	if(pReason && pReason[0])
+		Send(pReason);
+
+	net_tcp_close(m_Socket);
+
+	Reset();
+}
+
+int CConsoleNetConnection::Update()
+{
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if((int)(sizeof(m_aBuffer)) <= m_BufferOffset)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString));
+			return -1;
+		}
+
+		int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset);
+
+		if(Bytes > 0)
+		{
+			m_BufferOffset += Bytes;
+		}
+		else if(Bytes < 0)
+		{
+			if(net_would_block()) // no data received
+				return 0;
+
+			m_State = NET_CONNSTATE_ERROR; // error
+			str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString));
+			return -1;
+		}
+		else
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString));
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int CConsoleNetConnection::Recv(char *pLine, int MaxLength)
+{
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if(m_BufferOffset)
+		{
+			// find message start
+			int StartOffset = 0;
+			while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n')
+			{
+				// detect clients line ending format
+				if(!m_LineEndingDetected)
+				{
+					m_aLineEnding[0] = m_aBuffer[StartOffset];
+					if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') &&
+						m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1])
+						m_aLineEnding[1] = m_aBuffer[StartOffset+1];
+					m_LineEndingDetected = true;
+				}
+
+				if(++StartOffset >= m_BufferOffset)
+				{
+					m_BufferOffset = 0;
+					return 0;
+				}
+			}
+
+			// find message end
+			int EndOffset = StartOffset;
+			while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n')
+			{
+				if(++EndOffset >= m_BufferOffset)
+				{
+					if(StartOffset > 0)
+					{
+						mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
+						m_BufferOffset -= StartOffset;
+					}
+					return 0;
+				}
+			}
+
+			// extract message and update buffer
+			if(MaxLength-1 < EndOffset-StartOffset)
+			{
+				if(StartOffset > 0)
+				{
+					mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
+					m_BufferOffset -= StartOffset;
+				}
+				return 0;
+			}
+			mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset);
+			pLine[EndOffset-StartOffset] = 0;
+			str_sanitize_cc(pLine);
+			mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset);
+			m_BufferOffset -= EndOffset;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CConsoleNetConnection::Send(const char *pLine)
+{
+	if(State() != NET_CONNSTATE_ONLINE)
+		return -1;
+
+	char aBuf[1024];
+	str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2);
+	int Length = str_length(aBuf);
+	aBuf[Length] = m_aLineEnding[0];
+	aBuf[Length+1] = m_aLineEnding[1];
+	aBuf[Length+2] = m_aLineEnding[2];
+	Length += 3;
+	const char *pData = aBuf;
+
+	while(true)
+	{
+		int Send = net_tcp_send(m_Socket, pData, Length);
+		if(Send < 0)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString));
+			return -1;
+		}
+
+		if(Send >= Length)
+			break;
+
+		pData += Send;
+		Length -= Send;
+	}
+	
+	return 0;
+}
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index f3fbfa32..b100e1a2 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -221,10 +221,10 @@ int CNetServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason)
 		char Buf[128];
 		NETADDR BanAddr;
 
-		int Mins = (Seconds + 59) / 60;
-		if(Mins)
+		if(Stamp > -1)
 		{
-			if(Mins == 1)
+			int Mins = (Seconds + 59) / 60;
+			if(Mins <= 1)
 				str_format(Buf, sizeof(Buf), "You have been banned for 1 minute (%s)", pReason);
 			else
 				str_format(Buf, sizeof(Buf), "You have been banned for %d minutes (%s)", Mins, pReason);
@@ -255,7 +255,7 @@ int CNetServer::Update()
 	}
 
 	// remove expired bans
-	while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now)
+	while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires > -1 && m_BanPool_FirstUsed->m_Info.m_Expires < Now)
 	{
 		CBan *pBan = m_BanPool_FirstUsed;
 		BanRemoveByObject(pBan);
@@ -307,10 +307,10 @@ int CNetServer::Recv(CNetChunk *pChunk)
 			{
 				// banned, reply with a message
 				char BanStr[128];
-				if(pBan->m_Info.m_Expires)
+				if(pBan->m_Info.m_Expires > -1)
 				{
 					int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60;
-					if(Mins == 1)
+					if(Mins <= 1)
 						str_format(BanStr, sizeof(BanStr), "Banned for 1 minute (%s)", pBan->m_Info.m_Reason);
 					else
 						str_format(BanStr, sizeof(BanStr), "Banned for %d minutes (%s)", Mins, pBan->m_Info.m_Reason);
diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp
index 9b16ce0d..f2e9e65d 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -662,6 +662,16 @@ void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData)
 	((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr);
 }
 
+void CGameConsole::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments() == 1)
+	{
+		CGameConsole *pThis = static_cast<CGameConsole *>(pUserData);
+		pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0));
+	}
+}
+
 void CGameConsole::PrintLine(int Type, const char *pLine)
 {
 	if(Type == CONSOLETYPE_LOCAL)
@@ -679,7 +689,7 @@ void CGameConsole::OnConsoleInit()
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
 
 	//
-	Console()->RegisterPrintCallback(ClientConsolePrintCallback, this);
+	m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, ClientConsolePrintCallback, this);
 
 	Console()->Register("toggle_local_console", "", CFGFLAG_CLIENT, ConToggleLocalConsole, this, "Toggle local console");
 	Console()->Register("toggle_remote_console", "", CFGFLAG_CLIENT, ConToggleRemoteConsole, this, "Toggle remote console");
@@ -687,6 +697,8 @@ void CGameConsole::OnConsoleInit()
 	Console()->Register("clear_remote_console", "", CFGFLAG_CLIENT, ConClearRemoteConsole, this, "Clear remote console");
 	Console()->Register("dump_local_console", "", CFGFLAG_CLIENT, ConDumpLocalConsole, this, "Dump local console");
 	Console()->Register("dump_remote_console", "", CFGFLAG_CLIENT, ConDumpRemoteConsole, this, "Dump remote console");
+
+	Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this);
 }
 
 void CGameConsole::OnStateChange(int NewState, int OldState)
diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h
index 326fb076..6bcc75a6 100644
--- a/src/game/client/components/console.h
+++ b/src/game/client/components/console.h
@@ -60,6 +60,7 @@ class CGameConsole : public CComponent
 
 	CInstance *CurrentConsole();
 	float TimeNow();
+	int m_PrintCBIndex;
 
 	int m_ConsoleType;
 	int m_ConsoleState;
@@ -77,6 +78,7 @@ class CGameConsole : public CComponent
 	static void ConClearRemoteConsole(IConsole::IResult *pResult, void *pUserData);
 	static void ConDumpLocalConsole(IConsole::IResult *pResult, void *pUserData);
 	static void ConDumpRemoteConsole(IConsole::IResult *pResult, void *pUserData);
+	static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 public:
 	enum
diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp
index d6b30fe0..6daf3c2e 100644
--- a/src/game/client/components/countryflags.cpp
+++ b/src/game/client/components/countryflags.cpp
@@ -69,6 +69,7 @@ void CCountryFlags::LoadCountryflagsIndexfile()
 		// add entry
 		CCountryFlag CountryFlag;
 		CountryFlag.m_CountryCode = CountryCode;
+		str_copy(CountryFlag.m_aCountryCodeString, aOrigin, sizeof(CountryFlag.m_aCountryCodeString));
 		CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
 		mem_free(Info.m_pData);
 		str_format(aBuf, sizeof(aBuf), "loaded country flag '%s'", aOrigin);
@@ -93,6 +94,7 @@ void CCountryFlags::OnInit()
 		CCountryFlag DummyEntry;
 		DummyEntry.m_CountryCode = -1;
 		DummyEntry.m_Texture = -1;
+		mem_zero(DummyEntry.m_aCountryCodeString, sizeof(DummyEntry.m_aCountryCodeString));
 		m_aCountryFlags.add(DummyEntry);
 	}
 }
diff --git a/src/game/client/components/countryflags.h b/src/game/client/components/countryflags.h
index 15eb8598..ad24a762 100644
--- a/src/game/client/components/countryflags.h
+++ b/src/game/client/components/countryflags.h
@@ -12,9 +12,10 @@ public:
 	struct CCountryFlag
 	{
 		int m_CountryCode;
+		char m_aCountryCodeString[8];
 		int m_Texture;
 
-		bool operator<(const CCountryFlag &Other) { return m_CountryCode < Other.m_CountryCode; }
+		bool operator<(const CCountryFlag &Other) { return str_comp(m_aCountryCodeString, Other.m_aCountryCodeString) < 0; }
 	};
 
 	void OnInit();
diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp
index 4f3d2da7..8f330f78 100644
--- a/src/game/client/components/menus.cpp
+++ b/src/game/client/components/menus.cpp
@@ -1125,7 +1125,9 @@ int CMenus::Render()
 				CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i);
 				if(Item.m_Visible)
 				{
-					Item.m_Rect.Margin(10.0f, &Item.m_Rect);
+					CUIRect Label;
+					Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+					Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
 					float OldWidth = Item.m_Rect.w;
 					Item.m_Rect.w = Item.m_Rect.h*2;
 					Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f;
@@ -1135,6 +1137,7 @@ int CMenus::Render()
 					IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
 					Graphics()->QuadsDrawTL(&QuadItem, 1);
 					Graphics()->QuadsEnd();
+					UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0);
 				}
 			}
 
diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp
index c2fab00f..51fdbd29 100644
--- a/src/game/client/components/menus_settings.cpp
+++ b/src/game/client/components/menus_settings.cpp
@@ -215,7 +215,9 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
 		CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i);
 		if(Item.m_Visible)
 		{
-			Item.m_Rect.Margin(10.0f, &Item.m_Rect);
+			CUIRect Label;
+			Item.m_Rect.Margin(5.0f, &Item.m_Rect);
+			Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
 			float OldWidth = Item.m_Rect.w;
 			Item.m_Rect.w = Item.m_Rect.h*2;
 			Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f;
@@ -225,6 +227,7 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
 			IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
 			Graphics()->QuadsDrawTL(&QuadItem, 1);
 			Graphics()->QuadsEnd();
+			UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0);
 		}
 	}
 
diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp
index 29b891c2..2de85d66 100644
--- a/src/game/client/lineinput.cpp
+++ b/src/game/client/lineinput.cpp
@@ -42,7 +42,7 @@ bool CLineInput::Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int *p
 
 		if (Len < StrMaxSize - CharSize && CursorPos < StrMaxSize - CharSize)
 		{
-			mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len - CursorPos + CharSize);
+			mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len-CursorPos+1); // +1 == null term
 			for(int i = 0; i < CharSize; i++)
 				pStr[CursorPos+i] = Tmp[i];
 			CursorPos += CharSize;
diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp
index 500e600b..5c897953 100644
--- a/src/game/editor/editor.cpp
+++ b/src/game/editor/editor.cpp
@@ -957,7 +957,7 @@ void CEditor::DoToolbar(CUIRect ToolBar)
 		if(DoButton_Editor(&s_BorderBut, "Border", pT?0:-1, &Button, 0, "Adds border tiles"))
 		{
 			if(pT)
-                DoMapBorder();
+				DoMapBorder();
 		}
 	}
 
diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp
index d2a1652c..af086df2 100644
--- a/src/game/gamecore.cpp
+++ b/src/game/gamecore.cpp
@@ -362,35 +362,37 @@ void CCharacterCore::Move()
 
 	m_Vel.x = m_Vel.x*RampValue;
 
-	vec2 NewPos = m_Pos;

+	vec2 NewPos = m_Pos;
 	m_pCollision->MoveBox(&NewPos, &m_Vel, vec2(28.0f, 28.0f), 0);
 
 	m_Vel.x = m_Vel.x*(1.0f/RampValue);
 
 	if(m_pWorld && m_pWorld->m_Tuning.m_PlayerCollision)
 	{
-		// check player collision

-		float Distance = distance(m_Pos, NewPos);

-		int End = Distance+1;

-		for(int i = 0; i < End; i++)

-		{

-			float a = i/Distance;

-			vec2 Pos = mix(m_Pos, NewPos, a);

-			for(int p = 0; p < MAX_CLIENTS; p++)

-			{

-				CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p];

-				if(!pCharCore || pCharCore == this)

-					continue;

-				float D = distance(Pos, pCharCore->m_Pos);

-				if(D < 28.0f*1.25f && D > 0.0f)

-				{

-					if(a > 0.0f)

-						m_Pos = Pos;

-					else

-						m_Pos = NewPos;

-					return;

-				}

-			}

+		// check player collision
+		float Distance = distance(m_Pos, NewPos);
+		int End = Distance+1;
+		vec2 LastPos = m_Pos;
+		for(int i = 0; i < End; i++)
+		{
+			float a = i/Distance;
+			vec2 Pos = mix(m_Pos, NewPos, a);
+			for(int p = 0; p < MAX_CLIENTS; p++)
+			{
+				CCharacterCore *pCharCore = m_pWorld->m_apCharacters[p];
+				if(!pCharCore || pCharCore == this)
+					continue;
+				float D = distance(Pos, pCharCore->m_Pos);
+				if(D < 28.0f && D > 0.0f)
+				{
+					if(a > 0.0f)
+						m_Pos = LastPos;
+					else if(distance(NewPos, pCharCore->m_Pos) > D)
+						m_Pos = NewPos;
+					return;
+				}
+			}
+			LastPos = Pos;
 		}
 	}
 
diff --git a/src/game/layers.cpp b/src/game/layers.cpp
index 82c0a61b..6deb0829 100644
--- a/src/game/layers.cpp
+++ b/src/game/layers.cpp
@@ -29,7 +29,7 @@ void CLayers::Init(class IKernel *pKernel)
 			if(pLayer->m_Type == LAYERTYPE_TILES)
 			{
 				CMapItemLayerTilemap *pTilemap = reinterpret_cast<CMapItemLayerTilemap *>(pLayer);
-				if(pTilemap->m_Flags&1)
+				if(pTilemap->m_Flags&TILESLAYERFLAG_GAME)
 				{
 					m_pGameLayer = pTilemap;
 					m_pGameGroup = pGroup;
diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp
index 9e2033b2..e926c305 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -179,7 +179,7 @@ void CCharacter::HandleNinja()
 				if(m_NumObjectsHit < 10)
 					m_apHitObjects[m_NumObjectsHit++] = aEnts[i];
 
-				aEnts[i]->TakeDamage(vec2(0, 10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA);
+				aEnts[i]->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA);
 			}
 		}
 
diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp
index af66fe0c..7278995f 100644
--- a/src/game/server/entities/laser.cpp
+++ b/src/game/server/entities/laser.cpp
@@ -21,15 +21,15 @@ CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEner
 bool CLaser::HitCharacter(vec2 From, vec2 To)
 {
 	vec2 At;
-	CCharacter *OwnerChar = GameServer()->GetPlayerChar(m_Owner);
-	CCharacter *Hit = GameServer()->m_World.IntersectCharacter(m_Pos, To, 0.f, At, OwnerChar);
-	if(!Hit)
+	CCharacter *pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
+	CCharacter *pHit = GameServer()->m_World.IntersectCharacter(m_Pos, To, 0.f, At, pOwnerChar);
+	if(!pHit)
 		return false;
 
 	m_From = From;
 	m_Pos = At;
 	m_Energy = -1;
-	Hit->TakeDamage(vec2(0.f, 0.f), GameServer()->Tuning()->m_LaserDamage, m_Owner, WEAPON_RIFLE);
+	pHit->TakeDamage(vec2(0.f, 0.f), GameServer()->Tuning()->m_LaserDamage, m_Owner, WEAPON_RIFLE);
 	return true;
 }
 
diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp
index fd574d85..f8d418c3 100644
--- a/src/game/server/gamecontroller.cpp
+++ b/src/game/server/gamecontroller.cpp
@@ -217,6 +217,7 @@ void IGameController::StartRound()
 	m_aTeamscore[TEAM_RED] = 0;
 	m_aTeamscore[TEAM_BLUE] = 0;
 	m_ForceBalanced = false;
+	Server()->DemoRecorder_HandleAutoStart();
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags&GAMEFLAG_TEAMS);
 	GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
diff --git a/src/game/version.h b/src/game/version.h
index c7f04b75..76b6d4ab 100644
--- a/src/game/version.h
+++ b/src/game/version.h
@@ -3,6 +3,6 @@
 #ifndef GAME_VERSION_H
 #define GAME_VERSION_H
 #include "generated/nethash.cpp"
-#define GAME_VERSION "0.6 trunk"
+#define GAME_VERSION "0.6.1"
 #define GAME_NETVERSION "0.6 " GAME_NETVERSION_HASH
 #endif