76 changes: 62 additions & 14 deletions data/languages/polish.txt
Expand Up @@ -1052,8 +1052,8 @@ Please enter your nickname below.
Show DDNet map finishes in server browser
== Pokazuj ukończone mapy DDNet w wyszukiwarce

transmits your player name to info2.ddnet.tw
== przenosi twój nick do info2.ddnet.tw
transmits your player name to info.ddnet.org
== przenosi twój nick do info.ddnet.org

Indicate map finish
== Pokazuj ukończone mapy
Expand Down Expand Up @@ -1169,8 +1169,8 @@ Particles
Assets directory
== Katalog zasobów

https://wiki.ddnet.tw/
== https://wiki.ddnet.tw/
https://wiki.ddnet.org/
== https://wiki.ddnet.org/

Website
== Strona
Expand Down Expand Up @@ -1290,12 +1290,6 @@ auto
Kill Messages
== Wiadomości o zabiciu

Laser Outline Color
== Zewnętrzny kolor lasera

Laser Inner Color
== Wewnętrzny kolor lasera

Preview
== Podgląd

Expand Down Expand Up @@ -1335,8 +1329,8 @@ Chat command (e.g. showall 1)
Discord
== Discord

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

Tutorial
== Samouczek
Expand Down Expand Up @@ -1407,16 +1401,43 @@ Requesting to join the game
Loading menu images
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Loading ghost files
==

Download community skins
Loading skin files
==

Loading skin files
Download community skins
==

Enable controller
Expand Down Expand Up @@ -1550,6 +1571,33 @@ Normal Color
Highlight Color
==

Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

Extras
==

Expand Down
76 changes: 62 additions & 14 deletions data/languages/portuguese.txt
Expand Up @@ -861,8 +861,8 @@ Speed
Video name:
== Nome do vídeo:

transmits your player name to info2.ddnet.tw
== transmite o teu nome de jogador para info2.ddnet.tw
transmits your player name to info.ddnet.org
== transmite o teu nome de jogador para info.ddnet.org

Theme
== Tema
Expand Down Expand Up @@ -951,14 +951,14 @@ Particles
Discord
== Discord

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

Learn
== Aprender

https://wiki.ddnet.tw/
== https://wiki.ddnet.tw/
https://wiki.ddnet.org/
== https://wiki.ddnet.org/wiki/Main_Page/pt-br

Website
== Website
Expand Down Expand Up @@ -1109,6 +1109,33 @@ Unfinished map
Leak IP
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Expand Down Expand Up @@ -1154,6 +1181,9 @@ Skip the main menu
Config directory
==

Loading skin files
==

Toggle to edit your dummy settings
==

Expand All @@ -1178,9 +1208,6 @@ Choose default eyes when joining a server
Skin Database
==

Loading skin files
==

Hook collisions
==

Expand Down Expand Up @@ -1411,10 +1438,34 @@ Normal Color
Highlight Color
==

Laser Outline Color
Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Entities
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Laser Inner Color
Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

Enable replays
Expand Down Expand Up @@ -1471,9 +1522,6 @@ Run on join
Chat command (e.g. showall 1)
==

Entities
==

Emoticons
==

Expand Down
70 changes: 59 additions & 11 deletions data/languages/romanian.txt
Expand Up @@ -778,7 +778,7 @@ Skip Tutorial
Show DDNet map finishes in server browser
==

transmits your player name to info2.ddnet.tw
transmits your player name to info.ddnet.org
==

Theme
Expand Down Expand Up @@ -859,6 +859,33 @@ Please use a different name
Remove chat
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Expand Down Expand Up @@ -955,6 +982,9 @@ Max CSVs
Dummy settings
==

Loading skin files
==

Toggle to edit your dummy settings
==

Expand Down Expand Up @@ -982,9 +1012,6 @@ Skin Database
Skins directory
==

Loading skin files
==

Hook collisions
==

Expand Down Expand Up @@ -1317,10 +1344,34 @@ Normal Color
Highlight Color
==

Laser Outline Color
Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Entities
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Laser Inner Color
Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

Save the best demo of each race
Expand Down Expand Up @@ -1431,9 +1482,6 @@ No updates available
Check now
==

Entities
==

Emoticons
==

Expand All @@ -1452,13 +1500,13 @@ Assets directory
Discord
==

https://ddnet.tw/discord
https://ddnet.org/discord
==

Learn
==

https://wiki.ddnet.tw/
https://wiki.ddnet.org/
==

Tutorial
Expand Down
72 changes: 60 additions & 12 deletions data/languages/russian.txt
Expand Up @@ -807,8 +807,8 @@ Video name:
Show DDNet map finishes in server browser
== Показывать в браузере статус завершения карты

transmits your player name to info2.ddnet.tw
== передает ваш псевдоним на info2.ddnet.tw
transmits your player name to info.ddnet.org
== передает ваш псевдоним на info.ddnet.org

Exclude
== Исключить
Expand Down Expand Up @@ -1077,8 +1077,8 @@ Markers
Skip the main menu
== Пропускать главное меню

https://wiki.ddnet.tw/
== https://wiki.ddnet.tw/wiki/Main_Page/ru
https://wiki.ddnet.org/
== https://wiki.ddnet.org/wiki/Main_Page/ru

Website
== Сайт
Expand Down Expand Up @@ -1204,12 +1204,6 @@ The width of texture %s is not divisible by %d, or the height is not divisible b
Dummy
== Дамми

Laser Outline Color
== Цвет обводки лазера

Laser Inner Color
== Цвет лазера

Preview
== Превью

Expand All @@ -1228,8 +1222,8 @@ Chat command
Discord
== Дискорд

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

AFR
== AFR
Expand Down Expand Up @@ -1565,3 +1559,57 @@ Loading sound files

Why are you slowmo replaying to read this?
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Freeze Laser Outline Color
==

Freeze Laser Inner Color
==
1,445 changes: 748 additions & 697 deletions data/languages/serbian.txt

Large diffs are not rendered by default.

334 changes: 191 additions & 143 deletions data/languages/serbian_cyrillic.txt

Large diffs are not rendered by default.

73 changes: 61 additions & 12 deletions data/languages/simplified_chinese.txt
Expand Up @@ -27,6 +27,7 @@
# 2022-06-27 cheeser0613
# 2022-07-04 cheeser0613
# 2022-08-08 cheeser0613
# 2022-09-17 cheeser0613
##### /authors #####

##### translated strings #####
Expand Down Expand Up @@ -624,7 +625,7 @@ Length:
== 时长:

Laser
== 激光枪
== 激光

Netversion:
== 通信版本:
Expand Down Expand Up @@ -926,8 +927,8 @@ Date
Show DDNet map finishes in server browser
== 在服务器浏览器中显示已完成的 DDNet 地图

transmits your player name to info2.ddnet.tw
== 将会发送你的玩家昵称到 info2.ddnet.tw
transmits your player name to info.ddnet.org
== 将会发送你的玩家昵称到 info.ddnet.org

Reload
== 刷新
Expand Down Expand Up @@ -1094,8 +1095,8 @@ Use k key to kill (restart), q to pause and watch other players. See settings fo
Country / Region
== 国家 / 地区

https://wiki.ddnet.tw/
== https://docs.qq.com/doc/DWGFrV0xPRmVWVkla
https://wiki.ddnet.org/
== https://wiki.ddnet.org/wiki/Main_Page/zh

Warning
== 警告
Expand Down Expand Up @@ -1218,7 +1219,7 @@ Particles
Assets directory
== 资源目录

https://ddnet.tw/discord
https://ddnet.org/discord
== http://chat.teeworlds.cn/

Discord
Expand All @@ -1233,12 +1234,6 @@ Chat command
Dummy
== 分身

Laser Outline Color
== 激光边框颜色

Laser Inner Color
== 激光颜色

Preview
== 预览

Expand Down Expand Up @@ -1585,3 +1580,57 @@ Loading race demo files

Loading sound files
== 正在加载声音文件

Play the current demo
== 播放回放

Pause the current demo
== 暂停回放

Stop the current demo
== 结束回放

Slow down the demo
== 减慢播放速度

Speed up the demo
== 加快播放速度

Mark the beginning of a cut
== 标记裁剪起点

Mark the end of a cut
== 标记裁剪终点

Export cut as a seperate demo
== 另存为新回放文件

Toggle keyboard shortcuts
== 启用/禁用键盘快捷键

Weapons
== 武器

Rifle Laser Outline Color
== 激光枪弹道外框颜色

Rifle Laser Inner Color
== 激光枪弹道实心颜色

Shotgun Laser Outline Color
== 霰弹枪弹道外框颜色

Shotgun Laser Inner Color
== 霰弹枪弹道实心颜色

Door Laser Outline Color
== 激光门外框颜色

Door Laser Inner Color
== 激光门实心颜色

Freeze Laser Outline Color
== 冻结激光外框颜色

Freeze Laser Inner Color
== 冻结激光实心颜色
70 changes: 59 additions & 11 deletions data/languages/slovak.txt
Expand Up @@ -769,7 +769,7 @@ Skip Tutorial
Show DDNet map finishes in server browser
==

transmits your player name to info2.ddnet.tw
transmits your player name to info.ddnet.org
==

Theme
Expand Down Expand Up @@ -850,6 +850,33 @@ Please use a different name
Remove chat
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Expand Down Expand Up @@ -946,6 +973,9 @@ Max CSVs
Dummy settings
==

Loading skin files
==

Toggle to edit your dummy settings
==

Expand Down Expand Up @@ -973,9 +1003,6 @@ Skin Database
Skins directory
==

Loading skin files
==

Hook collisions
==

Expand Down Expand Up @@ -1311,10 +1338,34 @@ Normal Color
Highlight Color
==

Laser Outline Color
Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Entities
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Laser Inner Color
Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

Save the best demo of each race
Expand Down Expand Up @@ -1425,9 +1476,6 @@ No updates available
Check now
==

Entities
==

Emoticons
==

Expand All @@ -1446,13 +1494,13 @@ Assets directory
Discord
==

https://ddnet.tw/discord
https://ddnet.org/discord
==

Learn
==

https://wiki.ddnet.tw/
https://wiki.ddnet.org/
==

Tutorial
Expand Down
72 changes: 60 additions & 12 deletions data/languages/spanish.txt
Expand Up @@ -690,8 +690,8 @@ Video name:
Show DDNet map finishes in server browser
== Marcar los mapas DDNet acabados en el navegador de servidores

transmits your player name to info2.ddnet.tw
== transmite tu nombre de jugador a info2.ddnet.tw
transmits your player name to info.ddnet.org
== transmite tu nombre de jugador a info.ddnet.org

Search
== Buscar
Expand Down Expand Up @@ -1167,8 +1167,8 @@ Particles
Assets directory
== Directorio de recursos

https://wiki.ddnet.tw/
== https://wiki.ddnet.tw/
https://wiki.ddnet.org/
== https://wiki.ddnet.org/wiki/Main_Page/es

Website
== Página web
Expand Down Expand Up @@ -1249,12 +1249,6 @@ Windowed borderless
Desktop fullscreen
== Pantalla completa de escritorio

Laser Outline Color
== Color de contorno del láser

Laser Inner Color
== Color interior del láser

Preview
== Vista previa

Expand All @@ -1270,8 +1264,8 @@ Regular Background Color
Discord
== Discord

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

Are you sure that you want to disconnect and switch to a different server?
== ¿Seguro que quieres desconectarte y cambiar de servidor?
Expand Down Expand Up @@ -1503,6 +1497,33 @@ Requesting to join the game
Loading menu images
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Expand Down Expand Up @@ -1563,6 +1584,33 @@ Something hookable
A Tee
==

Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

Loading assets
==

Expand Down
78 changes: 63 additions & 15 deletions data/languages/swedish.txt
Expand Up @@ -714,8 +714,8 @@ Personal best:
Show DDNet map finishes in server browser
== Visa DDNet bana avklarningar i server bläddraren

transmits your player name to info2.ddnet.tw
== skickar ditt spel namn till info2.ddnet.tw
transmits your player name to info.ddnet.org
== skickar ditt spel namn till info.ddnet.org

9+ new mentions
== 9+ nya nämningar
Expand Down Expand Up @@ -1092,8 +1092,8 @@ Download skins
Client message
== Klientmeddelande

https://wiki.ddnet.tw/
== https://wiki.ddnet.tw/
https://wiki.ddnet.org/
== https://wiki.ddnet.org/

Website
== Hemsida
Expand Down Expand Up @@ -1237,12 +1237,6 @@ Assets
Kill Messages
== Döds meddelanden

Laser Outline Color
== Laser Yttre Färg

Laser Inner Color
== Laser Inre Färg

Use old chat style
== Använd gammal chatt stil

Expand Down Expand Up @@ -1285,8 +1279,8 @@ Assets directory
Discord
== Discord

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

The format of texture %s is not RGBA which will cause visual bugs.
==
Expand Down Expand Up @@ -1354,12 +1348,42 @@ Skip Tutorial
Loading menu images
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Loading ghost files
==

Loading skin files
==

Toggle to edit your dummy settings
==

Expand All @@ -1369,9 +1393,6 @@ Download community skins
Choose default eyes when joining a server
==

Loading skin files
==

Enable controller
==

Expand Down Expand Up @@ -1521,6 +1542,33 @@ Normal Color
Highlight Color
==

Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

When you cross the start line, show a ghost tee replicating the movements of your best time
==

Expand Down
75 changes: 62 additions & 13 deletions data/languages/traditional_chinese.txt
Expand Up @@ -16,6 +16,7 @@
# 2022-06-16 cheeser0613
# 2022-06-27 cheeser0613
# 2022-08-08 cheeser0613
# 2022-09-17 cheeser0613
##### /authors #####

##### translated strings #####
Expand Down Expand Up @@ -613,7 +614,7 @@ Length:
== 長度:

Laser
== 鐳射槍
== 鐳射

Netversion:
== 通訊版本:
Expand Down Expand Up @@ -915,8 +916,8 @@ Date
Show DDNet map finishes in server browser
== 在伺服器瀏覽器中顯示已完成的 DDNet 地圖

transmits your player name to info2.ddnet.tw
== 將會發送您的玩家名稱到 info2.ddnet.tw
transmits your player name to info.ddnet.org
== 將會發送您的玩家名稱到 info.ddnet.org

Reload
== 重新整理
Expand Down Expand Up @@ -1083,8 +1084,8 @@ Use k key to kill (restart), q to pause and watch other players. See settings fo
Country / Region
== 國家/地區

https://wiki.ddnet.tw/
== https://docs.qq.com/doc/DWGFrV0xPRmVWVkla
https://wiki.ddnet.org/
== https://wiki.ddnet.org/wiki/Main_Page/zh

Warning
== 警告
Expand Down Expand Up @@ -1252,12 +1253,6 @@ Windowed borderless
Desktop fullscreen
== 無邊框全螢幕

Laser Outline Color
== 鐳射線邊框顏色

Laser Inner Color
== 鐳射線顏色

Preview
== 預覽

Expand All @@ -1273,8 +1268,8 @@ Regular Background Color
Discord
== Discord

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

Are you sure that you want to disconnect and switch to a different server?
== 您確定要中斷此伺服器并嘗試加入其他伺服器嗎?
Expand Down Expand Up @@ -1574,3 +1569,57 @@ Loading race demo files

Loading sound files
== 正在加載聲音檔案

Play the current demo
== 播放回放

Pause the current demo
== 暫停回放

Stop the current demo
== 結束回放

Slow down the demo
== 減慢播放速度

Speed up the demo
== 加快播放速度

Mark the beginning of a cut
== 標記裁剪起點

Mark the end of a cut
== 標記裁剪終點

Export cut as a seperate demo
== 另存爲新回放檔案

Toggle keyboard shortcuts
== 啓用/禁用鍵盤快捷鍵

Weapons
== 武器

Rifle Laser Outline Color
== 鐳射槍彈道外框顔色

Rifle Laser Inner Color
== 鐳射槍彈道實心顔色

Shotgun Laser Outline Color
== 霰彈槍彈道外框顔色

Shotgun Laser Inner Color
== 霰彈槍彈道實心顔色

Door Laser Outline Color
== 鐳射門外框顔色

Door Laser Inner Color
== 鐳射門實心顔色

Freeze Laser Outline Color
== 凍結鐳射外框顔色

Freeze Laser Inner Color
== 凍結鐳射實心顔色
74 changes: 61 additions & 13 deletions data/languages/turkish.txt
Expand Up @@ -678,8 +678,8 @@ Video name:
Show DDNet map finishes in server browser
== Tamamlanan DDNet haritalarını sunucu listesinde göster

transmits your player name to info2.ddnet.tw
== oyuncu adını info2.ddnet.tw ile paylaşır
transmits your player name to info.ddnet.org
== oyuncu adını info.ddnet.org ile paylaşır

Search
== Ara
Expand Down Expand Up @@ -1221,6 +1221,33 @@ Refreshing...
Leak IP
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Expand All @@ -1245,6 +1272,9 @@ Config directory
Themes directory
==

Loading skin files
==

Toggle to edit your dummy settings
==

Expand All @@ -1263,9 +1293,6 @@ Skin Database
Skins directory
==

Loading skin files
==

Chat command
==

Expand Down Expand Up @@ -1457,10 +1484,34 @@ Normal Color
Highlight Color
==

Laser Outline Color
Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Entities
==

Door Laser Outline Color
==

Laser Inner Color
Door Laser Inner Color
==

Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

When you cross the start line, show a ghost tee replicating the movements of your best time
Expand Down Expand Up @@ -1496,9 +1547,6 @@ Run on join
Chat command (e.g. showall 1)
==

Entities
==

Emoticons
==

Expand All @@ -1517,11 +1565,11 @@ Assets directory
Discord
==

https://ddnet.tw/discord
https://ddnet.org/discord
==

https://wiki.ddnet.tw/
==
https://wiki.ddnet.org/
== https://wiki.ddnet.org/wiki/Main_Page/tr

Tutorial
==
Expand Down
78 changes: 63 additions & 15 deletions data/languages/ukrainian.txt
Expand Up @@ -550,8 +550,8 @@ Video name:
Show DDNet map finishes in server browser
== Показувати завершені карти DDNet в браузері сервера

transmits your player name to info2.ddnet.tw
== передає Ваш нікнейм на info2.ddnet.tw
transmits your player name to info.ddnet.org
== передає Ваш нікнейм на info.ddnet.org

Search
== Пошук
Expand Down Expand Up @@ -1201,12 +1201,6 @@ Assets
Kill Messages
== Повідомлення вбивств

Laser Outline Color
== Колір обводки лазеру

Laser Inner Color
== Колір лазеру

Use old chat style
== Використовувати стиль старого чату

Expand Down Expand Up @@ -1243,11 +1237,11 @@ Assets directory
Discord
== Діскорд

https://ddnet.tw/discord
== https://ddnet.tw/discord
https://ddnet.org/discord
== https://ddnet.org/discord

https://wiki.ddnet.tw/
== https://wiki.ddnet.tw/
https://wiki.ddnet.org/
== https://wiki.ddnet.org/

Website
== Вебсайт
Expand Down Expand Up @@ -1337,6 +1331,33 @@ Skip Tutorial
Loading menu images
==

Play the current demo
==

Pause the current demo
==

Stop the current demo
==

Slow down the demo
==

Speed up the demo
==

Mark the beginning of a cut
==

Mark the end of a cut
==

Export cut as a seperate demo
==

Toggle keyboard shortcuts
==

Loading demo files
==

Expand All @@ -1349,6 +1370,9 @@ Settings file
Config directory
==

Loading skin files
==

Toggle to edit your dummy settings
==

Expand All @@ -1358,9 +1382,6 @@ Download community skins
Choose default eyes when joining a server
==

Loading skin files
==

Enable controller
==

Expand Down Expand Up @@ -1513,6 +1534,33 @@ Normal Color
Highlight Color
==

Weapons
==

Rifle Laser Outline Color
==

Rifle Laser Inner Color
==

Shotgun Laser Outline Color
==

Shotgun Laser Inner Color
==

Door Laser Outline Color
==

Door Laser Inner Color
==

Freeze Laser Outline Color
==

Freeze Laser Inner Color
==

When you cross the start line, show a ghost tee replicating the movements of your best time
==

Expand Down
Binary file removed data/skins/Aoe4leg.png
Binary file not shown.
4 changes: 0 additions & 4 deletions data/skins/license.txt
Expand Up @@ -39,9 +39,5 @@ wartee:
Copyright Obst
zlib license

Aoe4legs:
Copyright Aoe
CC0 license

All other skins:
All content is released under CC-BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0/).
87 changes: 58 additions & 29 deletions datasrc/content.py
Expand Up @@ -230,12 +230,9 @@ def FileList(fmt, num):
image_game = Image("game", "game.png")
image_emoticons = Image("emoticons", "emoticons.png")
image_speedup_arrow = Image("speedup_arrow", "editor/speed_arrow.png")
image_demobuttons = Image("demobuttons", "demo_buttons.png")
image_fileicons = Image("fileicons", "file_icons.png")
image_guibuttons = Image("guibuttons", "gui_buttons.png")
image_guiicons = Image("guiicons", "gui_icons.png")
image_arrow = Image("arrow", "arrow.png")
image_demobuttons2 = Image("demobuttons2", "demo_buttons2.png")
image_audio_source = Image("audio_source", "editor/audio_source.png")
image_strongweak = Image("strongweak", "strong_weak.png")
image_hud = Image("hud", "hud.png")
Expand All @@ -250,12 +247,9 @@ def FileList(fmt, num):
container.images.Add(Image("console_bg", "console.png"))
container.images.Add(Image("console_bar", "console_bar.png"))
container.images.Add(image_speedup_arrow)
container.images.Add(image_demobuttons)
container.images.Add(image_fileicons)
container.images.Add(image_guibuttons)
container.images.Add(image_guiicons)
container.images.Add(image_arrow)
container.images.Add(image_demobuttons2)
container.images.Add(image_audio_source)
container.images.Add(image_strongweak)
container.images.Add(image_hud)
Expand All @@ -275,11 +269,8 @@ def FileList(fmt, num):
set_tee = SpriteSet("tee", image_null, 8, 4)
set_emoticons = SpriteSet("emoticons", image_emoticons, 4, 4)
set_speedup_arrow = SpriteSet("speedup_arrow", image_speedup_arrow, 1, 1)
set_demobuttons = SpriteSet("demobuttons", image_demobuttons, 5, 1)
set_fileicons = SpriteSet("fileicons", image_fileicons, 8, 1)
set_guibuttons = SpriteSet("guibuttons", image_guibuttons, 12, 4)
set_guiicons = SpriteSet("guiicons", image_guiicons, 12, 2)
set_demobuttons2 = SpriteSet("demobuttons2", image_demobuttons2, 4, 1)
set_audio_source = SpriteSet("audio_source", image_audio_source, 1, 1)
set_strongweak = SpriteSet("strongweak", image_strongweak, 2, 1)
set_hud = SpriteSet("hud", image_hud, 16, 16)
Expand All @@ -290,11 +281,8 @@ def FileList(fmt, num):
container.spritesets.Add(set_tee)
container.spritesets.Add(set_emoticons)
container.spritesets.Add(set_speedup_arrow)
container.spritesets.Add(set_demobuttons)
container.spritesets.Add(set_fileicons)
container.spritesets.Add(set_guibuttons)
container.spritesets.Add(set_guiicons)
container.spritesets.Add(set_demobuttons2)
container.spritesets.Add(set_audio_source)
container.spritesets.Add(set_strongweak)
container.spritesets.Add(set_hud)
Expand Down Expand Up @@ -416,23 +404,6 @@ def FileList(fmt, num):

container.sprites.Add(Sprite("speedup_arrow", set_speedup_arrow, 0,0,1,1))

container.sprites.Add(Sprite("demobutton_play", set_demobuttons, 0,0,1,1))
container.sprites.Add(Sprite("demobutton_pause", set_demobuttons, 1,0,1,1))
container.sprites.Add(Sprite("demobutton_stop", set_demobuttons, 2,0,1,1))
container.sprites.Add(Sprite("demobutton_slower", set_demobuttons, 3,0,1,1))
container.sprites.Add(Sprite("demobutton_faster", set_demobuttons, 4,0,1,1))

container.sprites.Add(Sprite("demobutton_slice_begin", set_demobuttons2, 0,0,1,1))
container.sprites.Add(Sprite("demobutton_slice_end", set_demobuttons2, 1,0,1,1))
container.sprites.Add(Sprite("demobutton_shortcuts_enabled", set_demobuttons2, 2,0,1,1))
container.sprites.Add(Sprite("demobutton_shortcuts_disabled", set_demobuttons2, 3,0,1,1))

container.sprites.Add(Sprite("file_demo1", set_fileicons, 0,0,1,1))
container.sprites.Add(Sprite("file_demo2", set_fileicons, 1,0,1,1))
container.sprites.Add(Sprite("file_folder", set_fileicons, 2,0,1,1))
container.sprites.Add(Sprite("file_map1", set_fileicons, 5,0,1,1))
container.sprites.Add(Sprite("file_map2", set_fileicons, 6,0,1,1))

container.sprites.Add(Sprite("guibutton_off", set_guibuttons, 0,0,4,4))
container.sprites.Add(Sprite("guibutton_on", set_guibuttons, 4,0,4,4))
container.sprites.Add(Sprite("guibutton_hover", set_guibuttons, 8,0,4,4))
Expand Down Expand Up @@ -495,6 +466,18 @@ def FileList(fmt, num):
anim.front_foot.frames.Add(AnimKeyframe(0, 3, 0, -0.1))
container.animations.Add(anim)

anim = Animation("sit_left")
anim.body.frames.Add(AnimKeyframe(0, 0, 3, 0))
anim.back_foot.frames.Add(AnimKeyframe(0, -12, 0, 0.1))
anim.front_foot.frames.Add(AnimKeyframe(0, -8, 0, 0.1))
container.animations.Add(anim)

anim = Animation("sit_right")
anim.body.frames.Add(AnimKeyframe(0, 0, 3, 0))
anim.back_foot.frames.Add(AnimKeyframe(0, 12, 0, -0.1))
anim.front_foot.frames.Add(AnimKeyframe(0, 8, 0, -0.1))
container.animations.Add(anim)

anim = Animation("walk")
anim.body.frames.Add(AnimKeyframe(0.0, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(0.2, 0,-1, 0))
Expand All @@ -518,6 +501,52 @@ def FileList(fmt, num):
anim.front_foot.frames.Add(AnimKeyframe(1.0,-10,-4, 0.2))
container.animations.Add(anim)

anim = Animation("run_left")
anim.body.frames.Add(AnimKeyframe(0.0, 0, -2, 0))
anim.body.frames.Add(AnimKeyframe(0.2, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(0.4, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(0.6, 0, -2, 0))
anim.body.frames.Add(AnimKeyframe(0.8, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(1.0, 0, -2, 0))

anim.back_foot.frames.Add(AnimKeyframe(0.0, 18, -11, -0.27))
anim.back_foot.frames.Add(AnimKeyframe(0.2, 6, 0, 0))
anim.back_foot.frames.Add(AnimKeyframe(0.4, -7, 0, 0))
anim.back_foot.frames.Add(AnimKeyframe(0.6, -13, -4.5, 0.05))
anim.back_foot.frames.Add(AnimKeyframe(0.8, 0, -11, -0.2))
anim.back_foot.frames.Add(AnimKeyframe(1.0, 18, -11, -0.27))

anim.front_foot.frames.Add(AnimKeyframe(0.0, -13, -4.5, 0.05))
anim.front_foot.frames.Add(AnimKeyframe(0.2, -14, -7, 0.1))
anim.front_foot.frames.Add(AnimKeyframe(0.4, 11, -13, -0.3))
anim.front_foot.frames.Add(AnimKeyframe(0.6, 18, -11, -0.27))
anim.front_foot.frames.Add(AnimKeyframe(0.8, 3, 0, -0.02))
anim.front_foot.frames.Add(AnimKeyframe(1.0, -13, -4.5, 0.05))
container.animations.Add(anim)

anim = Animation("run_right")
anim.body.frames.Add(AnimKeyframe(0.0, 0, -2, 0))
anim.body.frames.Add(AnimKeyframe(0.2, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(0.4, 0, -2, 0))
anim.body.frames.Add(AnimKeyframe(0.6, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(0.8, 0, 0, 0))
anim.body.frames.Add(AnimKeyframe(1.0, 0, -2, 0))

anim.back_foot.frames.Add(AnimKeyframe(0.0, -18, -11, 0.27))
anim.back_foot.frames.Add(AnimKeyframe(0.2, 0, -11, 0.2))
anim.back_foot.frames.Add(AnimKeyframe(0.4, 13, -4.5, -0.05))
anim.back_foot.frames.Add(AnimKeyframe(0.6, 7, 0, 0))
anim.back_foot.frames.Add(AnimKeyframe(0.8, -6, 0, 0))
anim.back_foot.frames.Add(AnimKeyframe(1.0, -18, -11, 0.27))

anim.front_foot.frames.Add(AnimKeyframe(0.0, 13, -4.5, -0.05))
anim.front_foot.frames.Add(AnimKeyframe(0.2, -3, 0, 0.02))
anim.front_foot.frames.Add(AnimKeyframe(0.4, -18, -11, 0.27))
anim.front_foot.frames.Add(AnimKeyframe(0.6, -11, -13, 0.3))
anim.front_foot.frames.Add(AnimKeyframe(0.8, 14, -7, -0.1))
anim.front_foot.frames.Add(AnimKeyframe(1.0, 13, -4.5, -0.05))
container.animations.Add(anim)

anim = Animation("hammer_swing")
anim.attach.frames.Add(AnimKeyframe(0.0, 0, 0, -0.10))
anim.attach.frames.Add(AnimKeyframe(0.3, 0, 0, 0.25))
Expand Down
13 changes: 13 additions & 0 deletions datasrc/network.py
Expand Up @@ -34,6 +34,8 @@
"EXPLOSIVE", "FREEZE",
]

LaserTypes = ["RIFLE", "SHOTGUN", "DOOR", "FREEZE"]

Emoticons = ["OOP", "EXCLAMATION", "HEARTS", "DROP", "DOTDOT", "MUSIC", "SORRY", "GHOST", "SUSHI", "SPLATTEE", "DEVILTEE", "ZOMG", "ZZZ", "WTF", "EYES", "QUESTION"]

Powerups = ["HEALTH", "ARMOR", "WEAPON", "NINJA", "ARMOR_SHOTGUN", "ARMOR_GRENADE", "ARMOR_NINJA", "ARMOR_LASER"]
Expand Down Expand Up @@ -78,6 +80,7 @@
Enum("EMOTICON", Emoticons),
Enum("AUTHED", Authed),
Enum("ENTITYCLASS", EntityClasses),
Enum("LASERTYPE", LaserTypes),
]

Flags = [
Expand Down Expand Up @@ -273,6 +276,16 @@
NetTick("m_StartTick"),
]),

NetObjectEx("DDNetLaser", "laser@netobj.ddnet.tw", [
NetIntAny("m_ToX"),
NetIntAny("m_ToY"),
NetIntAny("m_FromX"),
NetIntAny("m_FromY"),
NetTick("m_StartTick"),
NetIntRange("m_Owner", 0, 'MAX_CLIENTS-1'),
NetIntAny("m_Type"),
]),

## Events

NetEvent("Common", [
Expand Down
10 changes: 5 additions & 5 deletions man/DDNet.adoc
Expand Up @@ -35,13 +35,13 @@ a *Teeworlds* modification with a unique cooperative gameplay.
Load _CONFIGURATION_FILE_ instead of default _settings_ddnet.cfg_

=== Other options
All options can be found here: https://ddnet.tw/settingscommands/
All options can be found here: https://ddnet.org/settingscommands/

== RESOURCES
- *Website*: https://ddnet.tw/
- *RSS*: https://ddnet.tw/feed/
- *Forum*: https://forum.ddnet.tw/
- *Discord*: https://ddnet.tw/discord
- *Website*: https://ddnet.org/
- *RSS*: https://ddnet.org/feed/
- *Forum*: https://forum.ddnet.org/
- *Discord*: https://ddnet.org/discord
- *Source*: https://github.com/ddnet/ddnet
== REPORTING BUGS
Expand Down
10 changes: 5 additions & 5 deletions man/DDNetServer.adoc
Expand Up @@ -35,13 +35,13 @@ a *Teeworlds* modification with a unique cooperative gameplay.
Load _CONFIGURATION_FILE_ instead of default _autoexec_server.cfg_

=== Other options
All options can be found here: https://ddnet.tw/settingscommands/
All options can be found here: https://ddnet.org/settingscommands/

== RESOURCES
- *Website*: https://ddnet.tw/
- *RSS*: https://ddnet.tw/feed/
- *Forum*: https://forum.ddnet.tw/
- *Discord*: https://ddnet.tw/discord
- *Website*: https://ddnet.org/
- *RSS*: https://ddnet.org/feed/
- *Forum*: https://forum.ddnet.org/
- *Discord*: https://ddnet.org/discord
- *Source*: https://github.com/ddnet/ddnet
== REPORTING BUGS
Expand Down
8 changes: 4 additions & 4 deletions other/ddnet.appdata.xml
Expand Up @@ -18,13 +18,13 @@
</description>
<screenshots>
<screenshot type="default">
<image>https://ddnet.tw/screenshots/1.png</image>
<image>https://ddnet.org/screenshots/1.png</image>
</screenshot>
<screenshot>
<image>https://ddnet.tw/screenshots/2.png</image>
<image>https://ddnet.org/screenshots/2.png</image>
</screenshot>
<screenshot>
<image>https://ddnet.tw/screenshots/3.png</image>
<image>https://ddnet.org/screenshots/3.png</image>
</screenshot>
</screenshots>
<keywords>
Expand Down Expand Up @@ -82,7 +82,7 @@
<release date="2020-05-01" version="13.1"/>
<release date="2020-04-10" version="13.0.2"/>
</releases>
<url type="homepage">https://ddnet.tw/</url>
<url type="homepage">https://ddnet.org/</url>
<url type="bugtracker">https://github.com/ddnet/ddnet/issues</url>
<url type="donation">https://www.paypal.me/ddnet</url>
<provides>
Expand Down
70 changes: 50 additions & 20 deletions scripts/integration_test.sh
Expand Up @@ -159,6 +159,7 @@ function wait_for_fifo() {
done
}

echo "[*] launch server"
$tool ../DDNet-Server \
"sv_input_fifo server.fifo;
sv_rcon_password rcon;
Expand All @@ -168,6 +169,7 @@ $tool ../DDNet-Server \
sv_register 0;
sv_port $port" > stdout_server.txt 2> stderr_server.txt || fail server "$?" &

echo "[*] launch client 1"
$tool ../DDNet \
"cl_input_fifo client1.fifo;
player_name client1;
Expand All @@ -179,12 +181,18 @@ $tool ../DDNet \

if [ "$arg_valgrind_memcheck" == "1" ]; then
wait_for_fifo client1.fifo 120
sleep 1
sleep 20
else
wait_for_fifo client1.fifo 50
sleep 1
fi

echo "[*] start demo recording"
echo "record server" > server.fifo
echo "record client1" > client1.fifo
sleep 1

echo "[*] launch client 2"
$tool ../DDNet \
"cl_input_fifo client2.fifo;
player_name client2;
Expand Down Expand Up @@ -225,6 +233,7 @@ say "/mc
;saytime"
EOF
sleep 1

echo "[*] test rcon commands"
tr -d '\n' > client1.fifo << EOF
rcon say hello from admin;
Expand All @@ -235,18 +244,40 @@ muteid 1 900 spam;
unban_all;
EOF
sleep 1

echo "[*] stop demo recording"
echo "stoprecord" > server.fifo
echo "stoprecord" > client1.fifo
sleep 1

echo "[*] test map change"
echo "rcon sv_map Tutorial" > client1.fifo
sleep 1
if [ "$arg_valgrind_memcheck" == "1" ]; then
sleep 30
else
sleep 15
fi

echo "[*] play demos"
echo "play demos/server.demo" > client1.fifo
echo "play demos/client1.demo" > client2.fifo
if [ "$arg_valgrind_memcheck" == "1" ]; then
sleep 20
else
sleep 5
fi

# TODO: remove the first grep after https://github.com/ddnet/ddnet/pull/5036 is merged
if ! grep -qE '^\[[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2}\]\[chat\]: 0:-2:client1: hello world$' server.log && \
! grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2} D chat: 0:-2:client1: hello world$' server.log
# Kill all processes first so all outputs are fully written
kill_all
wait
sleep 1

if ! grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:){2}[0-9]{2} I chat: 0:-2:client1: hello world$' server.log
then
touch fail_chat.txt
echo "[-] Error: chat message not found in server log"
fi

if ! grep -q 'cmdlist' client1.log || \
! grep -q 'pause' client1.log || \
! grep -q 'rank' client1.log || \
Expand All @@ -262,10 +293,16 @@ then
echo "[-] Error: admin message not found in server log"
fi

kill_all
wait

sleep 1
if ! grep -q "demo_player: Stopped playback" client1.log
then
touch fail_demo_server.txt
echo "[-] Error: demo playback of server demo in client 1 was not started/finished"
fi
if ! grep -q "demo_player: Stopped playback" client2.log
then
touch fail_demo_client.txt
echo "[-] Error: demo playback of client demo in client 2 was not started/finished"
fi

ranks="$(sqlite3 ddnet-server.sqlite < <(echo "select * from record_race;"))"
num_ranks="$(echo "$ranks" | wc -l | xargs)"
Expand Down Expand Up @@ -316,23 +353,16 @@ do
then
continue
fi
if [ "$arg_verbose" != "1" ]
then
continue
fi
echo "[!] Warning: $stderr"
cat "$stderr"
done

if test -n "$(find . -maxdepth 1 -name 'fail_*' -print -quit)"
then
if [ "$arg_verbose" == "1" ]
then
for fail in fail_*
do
cat "$fail"
done
fi
for fail in fail_*
do
cat "$fail"
done
print_results
echo "[-] Test failed. See errors above."
exit 1
Expand Down
2 changes: 1 addition & 1 deletion src/base/lock_scope.h
Expand Up @@ -6,7 +6,7 @@
class SCOPED_CAPABILITY CLockScope
{
public:
CLockScope(LOCK Lock) ACQUIRE(Lock, m_Lock) REQUIRES(!Lock, !m_Lock) :
CLockScope(LOCK Lock) ACQUIRE(Lock, m_Lock) :
m_Lock(Lock)
{
lock_wait(m_Lock);
Expand Down
2 changes: 1 addition & 1 deletion src/base/log.cpp
Expand Up @@ -106,7 +106,7 @@ void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys
Msg.m_SystemLength = str_length(Msg.m_aSystem);

// TODO: Add level?
str_format(Msg.m_aLine, sizeof(Msg.m_aLine), "[%s][%s]: ", Msg.m_aTimestamp, Msg.m_aSystem);
str_format(Msg.m_aLine, sizeof(Msg.m_aLine), "%s %c %s: ", Msg.m_aTimestamp, "EWIDT"[level], Msg.m_aSystem);
Msg.m_LineMessageOffset = str_length(Msg.m_aLine);

char *pMessage = Msg.m_aLine + Msg.m_LineMessageOffset;
Expand Down
136 changes: 109 additions & 27 deletions src/base/system.cpp
Expand Up @@ -64,10 +64,13 @@
#include <ws2tcpip.h>

#include <cerrno>
#include <float.h>
#include <io.h>
#include <objbase.h>
#include <process.h>
#include <share.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <wincrypt.h>
#else
#error NOT IMPLEMENTED
Expand Down Expand Up @@ -146,6 +149,7 @@ typedef struct
} NETSOCKET_BUFFER;

void net_buffer_init(NETSOCKET_BUFFER *buffer);
void net_buffer_reinit(NETSOCKET_BUFFER *buffer);
void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size);

struct NETSOCKET_INTERNAL
Expand Down Expand Up @@ -903,16 +907,18 @@ void sphore_destroy(SEMAPHORE *sem) { CloseHandle((HANDLE)*sem); }
void sphore_init(SEMAPHORE *sem)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "/%d-ddnet.tw-%p", pid(), (void *)sem);
str_format(aBuf, sizeof(aBuf), "%p", (void *)sem);
*sem = sem_open(aBuf, O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, 0);
if(*sem == SEM_FAILED)
dbg_msg("sphore", "init failed: %d", errno);
}
void sphore_wait(SEMAPHORE *sem) { sem_wait(*sem); }
void sphore_signal(SEMAPHORE *sem) { sem_post(*sem); }
void sphore_destroy(SEMAPHORE *sem)
{
char aBuf[64];
sem_close(*sem);
str_format(aBuf, sizeof(aBuf), "/%d-ddnet.tw-%p", pid(), (void *)sem);
str_format(aBuf, sizeof(aBuf), "%p", (void *)sem);
sem_unlink(aBuf);
}
#elif defined(CONF_FAMILY_UNIX)
Expand Down Expand Up @@ -1693,6 +1699,16 @@ void net_buffer_init(NETSOCKET_BUFFER *buffer)
#endif
}

void net_buffer_reinit(NETSOCKET_BUFFER *buffer)
{
#if defined(CONF_PLATFORM_LINUX)
for(int i = 0; i < VLEN; i++)
{
buffer->msgs[i].msg_hdr.msg_namelen = sizeof(buffer->sockaddrs[i]);
}
#endif
}

void net_buffer_simple(NETSOCKET_BUFFER *buffer, char **buf, int *size)
{
#if defined(CONF_PLATFORM_LINUX)
Expand All @@ -1714,6 +1730,7 @@ int net_udp_recv(NETSOCKET sock, NETADDR *addr, unsigned char **data)
{
if(sock->buffer.pos >= sock->buffer.size)
{
net_buffer_reinit(&sock->buffer);
sock->buffer.size = recvmmsg(sock->ipv4sock, sock->buffer.msgs, VLEN, 0, NULL);
sock->buffer.pos = 0;
}
Expand All @@ -1723,6 +1740,7 @@ int net_udp_recv(NETSOCKET sock, NETADDR *addr, unsigned char **data)
{
if(sock->buffer.pos >= sock->buffer.size)
{
net_buffer_reinit(&sock->buffer);
sock->buffer.size = recvmmsg(sock->ipv6sock, sock->buffer.msgs, VLEN, 0, NULL);
sock->buffer.pos = 0;
}
Expand Down Expand Up @@ -2115,7 +2133,7 @@ void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user)
while((entry = readdir(d)) != NULL)
{
str_copy(buffer + length, entry->d_name, (int)sizeof(buffer) - length);
if(cb(entry->d_name, entry->d_type == DT_UNKNOWN ? fs_is_dir(buffer) : entry->d_type == DT_DIR, type, user))
if(cb(entry->d_name, fs_is_dir(buffer), type, user))
break;
}

Expand Down Expand Up @@ -2184,7 +2202,7 @@ void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int t
info.m_TimeCreated = created;
info.m_TimeModified = modified;

if(cb(&info, entry->d_type == DT_UNKNOWN ? fs_is_dir(buffer) : entry->d_type == DT_DIR, type, user))
if(cb(&info, fs_is_dir(buffer), type, user))
break;
}

Expand Down Expand Up @@ -2309,6 +2327,17 @@ int fs_is_dir(const char *path)
#endif
}

int fs_is_relative_path(const char *path)
{
#if defined(CONF_FAMILY_WINDOWS)
WCHAR wPath[IO_MAX_PATH_LENGTH];
MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, std::size(wPath));
return PathIsRelativeW(wPath) ? 1 : 0;
#else
return path[0] == '/' ? 0 : 1; // yes, it's that simple
#endif
}

int fs_chdir(const char *path)
{
if(fs_is_dir(path))
Expand Down Expand Up @@ -2634,6 +2663,20 @@ char *str_trim_words(char *str, int words)
return str;
}

bool str_has_cc(const char *str)
{
unsigned char *s = (unsigned char *)str;
while(*s)
{
if(*s < 32)
{
return true;
}
s++;
}
return false;
}

/* makes sure that the string only contains the characters between 32 and 255 */
void str_sanitize_cc(char *str_in)
{
Expand Down Expand Up @@ -2984,6 +3027,18 @@ const char *str_rchr(const char *haystack, char needle)
return strrchr(haystack, needle);
}

int str_countchr(const char *haystack, char needle)
{
int count = 0;
while(*haystack)
{
if(*haystack == needle)
count++;
haystack++;
}
return count;
}

void str_hex(char *dst, int dst_size, const void *data, int data_size)
{
static const char hex[] = "0123456789ABCDEF";
Expand Down Expand Up @@ -3028,11 +3083,10 @@ static int hexval(char x)
}
}

static int byteval(const char *byte, unsigned char *dst)
static int byteval(const char *hex, unsigned char *dst)
{
int v1 = -1, v2 = -1;
v1 = hexval(byte[0]);
v2 = hexval(byte[1]);
int v1 = hexval(hex[0]);
int v2 = hexval(hex[1]);

if(v1 < 0 || v2 < 0)
return 1;
Expand Down Expand Up @@ -3541,9 +3595,9 @@ int str_utf8_encode(char *ptr, int chr)

static unsigned char str_byte_next(const char **ptr)
{
unsigned char byte = **ptr;
unsigned char byte_value = **ptr;
(*ptr)++;
return byte;
return byte_value;
}

static void str_byte_rewind(const char **ptr)
Expand All @@ -3561,35 +3615,35 @@ int str_utf8_decode(const char **ptr)
int utf8_bytes_needed = 0;
while(true)
{
unsigned char byte = str_byte_next(ptr);
unsigned char byte_value = str_byte_next(ptr);
if(utf8_bytes_needed == 0)
{
if(byte <= 0x7F)
if(byte_value <= 0x7F)
{
return byte;
return byte_value;
}
else if(0xC2 <= byte && byte <= 0xDF)
else if(0xC2 <= byte_value && byte_value <= 0xDF)
{
utf8_bytes_needed = 1;
utf8_code_point = byte - 0xC0;
utf8_code_point = byte_value - 0xC0;
}
else if(0xE0 <= byte && byte <= 0xEF)
else if(0xE0 <= byte_value && byte_value <= 0xEF)
{
if(byte == 0xE0)
if(byte_value == 0xE0)
utf8_lower_boundary = 0xA0;
if(byte == 0xED)
if(byte_value == 0xED)
utf8_upper_boundary = 0x9F;
utf8_bytes_needed = 2;
utf8_code_point = byte - 0xE0;
utf8_code_point = byte_value - 0xE0;
}
else if(0xF0 <= byte && byte <= 0xF4)
else if(0xF0 <= byte_value && byte_value <= 0xF4)
{
if(byte == 0xF0)
if(byte_value == 0xF0)
utf8_lower_boundary = 0x90;
if(byte == 0xF4)
if(byte_value == 0xF4)
utf8_upper_boundary = 0x8F;
utf8_bytes_needed = 3;
utf8_code_point = byte - 0xF0;
utf8_code_point = byte_value - 0xF0;
}
else
{
Expand All @@ -3598,7 +3652,7 @@ int str_utf8_decode(const char **ptr)
utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed);
continue;
}
if(!(utf8_lower_boundary <= byte && byte <= utf8_upper_boundary))
if(!(utf8_lower_boundary <= byte_value && byte_value <= utf8_upper_boundary))
{
// Resetting variables not necessary, will be done when
// the function is called again.
Expand All @@ -3608,7 +3662,7 @@ int str_utf8_decode(const char **ptr)
utf8_lower_boundary = 0x80;
utf8_upper_boundary = 0xBF;
utf8_bytes_seen += 1;
utf8_code_point = utf8_code_point + ((byte - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)));
utf8_code_point = utf8_code_point + ((byte_value - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)));
if(utf8_bytes_seen != utf8_bytes_needed)
{
continue;
Expand Down Expand Up @@ -3810,7 +3864,10 @@ PROCESS shell_execute(const char *file)
info.lpFile = wBuffer;
info.nShow = SW_SHOWMINNOACTIVE;
info.fMask = SEE_MASK_NOCLOSEPROCESS;
// Save and restore the FPU control word because ShellExecute might change it
unsigned oldcontrol87 = _control87(0u, 0u);
ShellExecuteExW(&info);
_control87(oldcontrol87, 0xffffffffu);
return info.hProcess;
#elif defined(CONF_FAMILY_UNIX)
char *argv[2];
Expand Down Expand Up @@ -3847,7 +3904,11 @@ int open_link(const char *link)
#if defined(CONF_FAMILY_WINDOWS)
WCHAR wBuffer[512];
MultiByteToWideChar(CP_UTF8, 0, link, -1, wBuffer, std::size(wBuffer));
return (uintptr_t)ShellExecuteW(NULL, L"open", wBuffer, NULL, NULL, SW_SHOWDEFAULT) > 32;
// Save and restore the FPU control word because ShellExecute might change it
unsigned oldcontrol87 = _control87(0u, 0u);
int status = (uintptr_t)ShellExecuteW(NULL, L"open", wBuffer, NULL, NULL, SW_SHOWDEFAULT) > 32;
_control87(oldcontrol87, 0xffffffffu);
return status;
#elif defined(CONF_PLATFORM_LINUX)
const int pid = fork();
if(pid == 0)
Expand All @@ -3866,8 +3927,18 @@ int open_file(const char *path)
#if defined(CONF_PLATFORM_MACOS)
return open_link(path);
#else
// Create a file link so the path can contain forward and
// backward slashes. But the file link must be absolute.
char buf[512];
str_format(buf, sizeof(buf), "file://%s", path);
char workingDir[IO_MAX_PATH_LENGTH];
if(fs_is_relative_path(path))
{
fs_getcwd(workingDir, sizeof(workingDir));
str_append(workingDir, "/", sizeof(workingDir));
}
else
workingDir[0] = '\0';
str_format(buf, sizeof(buf), "file://%s%s", workingDir, path);
return open_link(buf);
#endif
}
Expand Down Expand Up @@ -4177,6 +4248,17 @@ int net_socket_read_wait(NETSOCKET sock, std::chrono::nanoseconds nanoseconds)
return ::net_socket_read_wait(sock, (nanoseconds / std::chrono::nanoseconds(1us).count()).count());
}

#if defined(CONF_FAMILY_WINDOWS)
CWindowsComLifecycle::CWindowsComLifecycle()
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
}
CWindowsComLifecycle::~CWindowsComLifecycle()
{
CoUninitialize();
}
#endif

size_t std::hash<NETADDR>::operator()(const NETADDR &Addr) const noexcept
{
return std::hash<std::string_view>{}(std::string_view((const char *)&Addr, sizeof(Addr)));
Expand Down
52 changes: 52 additions & 0 deletions src/base/system.h
Expand Up @@ -1249,6 +1249,19 @@ int str_format(char *buffer, int buffer_size, const char *format, ...)
*/
char *str_trim_words(char *str, int words);

/**
* Check whether string has ASCII control characters.
*
* @ingroup Strings
*
* @param str String to check.
*
* @return Whether the string has ASCII control characters.
*
* @remark The strings are treated as zero-terminated strings.
*/
bool str_has_cc(const char *str);

/**
* Replaces all characters below 32 with whitespace.
*
Expand Down Expand Up @@ -1588,6 +1601,23 @@ const char *str_find(const char *haystack, const char *needle);
*/
const char *str_rchr(const char *haystack, char needle);

/*
Function: str_countchr
Counts the number of occurrences of a character in a string.
Parameters:
haystack - String to count in
needle - Character to count
Returns:
The number of characters in the haystack string matching
the needle character.
Remarks:
- The strings are treated as zero-terminated strings.
*/
int str_countchr(const char *haystack, char needle);

/*
Function: str_hex
Takes a datablock and generates a hex string of it, with spaces
Expand Down Expand Up @@ -1821,6 +1851,15 @@ int fs_storage_path(const char *appname, char *path, int max);
*/
int fs_is_dir(const char *path);

/*
Function: fs_is_relative_path
Checks whether a given path is relative or absolute.
Returns:
Returns 1 if relative, 0 if absolute.
*/
int fs_is_relative_path(const char *path);

/*
Function: fs_chdir
Changes current working directory
Expand Down Expand Up @@ -2506,6 +2545,19 @@ class CCmdlineFix
}
};

#if defined(CONF_FAMILY_WINDOWS)
/**
* This is a RAII wrapper to initialize/uninitialize the Windows COM library,
* which may be necessary for using the open_file and open_link functions.
*/
class CWindowsComLifecycle
{
public:
CWindowsComLifecycle();
~CWindowsComLifecycle();
};
#endif

/**
* Copies a string to a fixed-size array of chars.
*
Expand Down
102 changes: 71 additions & 31 deletions src/engine/client/client.cpp
Expand Up @@ -359,6 +359,7 @@ CClient::CClient() :
m_aMapDetailsName[0] = 0;
m_MapDetailsSha256 = SHA256_ZEROED;
m_MapDetailsCrc = 0;
m_aMapDetailsUrl[0] = 0;

IStorage::FormatTmpPath(m_aDDNetInfoTmp, sizeof(m_aDDNetInfoTmp), DDNET_INFO);
m_pDDNetInfoTask = NULL;
Expand Down Expand Up @@ -1299,7 +1300,6 @@ static void FormatMapDownloadFilename(const char *pName, const SHA256_DIGEST *pS

const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc)
{
const char *pError = 0;
char aBuf[512];
char aWanted[SHA256_MAXSTRSIZE + 16];
aWanted[0] = 0;
Expand All @@ -1316,32 +1316,38 @@ const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedS

// try the normal maps folder
str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
const char *pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError)
return pError;
return nullptr;

// try the downloaded maps
FormatMapDownloadFilename(pMapName, pWantedSha256, WantedCrc, false, aBuf, sizeof(aBuf));
pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError)
return pError;
return nullptr;

// backward compatibility with old names
if(pWantedSha256)
{
FormatMapDownloadFilename(pMapName, 0, WantedCrc, false, aBuf, sizeof(aBuf));
pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError)
return pError;
return nullptr;
}

// search for the map within subfolders
char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), "%s.map", pMapName);
if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf)))
{
pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError)
return nullptr;
}

return pError;
static char s_aErrorMsg[256];
str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "Could not find map '%s'", pMapName);
return s_aErrorMsg;
}

void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
Expand Down Expand Up @@ -1653,16 +1659,25 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC | CUnpacker::SKIP_START_WHITESPACES);
SHA256_DIGEST *pMapSha256 = (SHA256_DIGEST *)Unpacker.GetRaw(sizeof(*pMapSha256));
int MapCrc = Unpacker.GetInt();
int MapSize = Unpacker.GetInt();

if(Unpacker.Error())
{
return;
}

const char *pMapUrl = Unpacker.GetString(CUnpacker::SANITIZE_CC);
if(Unpacker.Error())
{
pMapUrl = "";
}

m_MapDetailsPresent = true;
(void)MapSize;
str_copy(m_aMapDetailsName, pMap);
m_MapDetailsSha256 = *pMapSha256;
m_MapDetailsCrc = MapCrc;
str_copy(m_aMapDetailsUrl, pMapUrl);
}
else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CAPABILITIES)
{
Expand Down Expand Up @@ -1715,9 +1730,11 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
else
{
SHA256_DIGEST *pMapSha256 = 0;
const char *pMapUrl = nullptr;
if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc)
{
pMapSha256 = &m_MapDetailsSha256;
pMapUrl = m_aMapDetailsUrl[0] ? m_aMapDetailsUrl : nullptr;
}
pError = LoadMapSearch(pMap, pMapSha256, MapCrc);

Expand Down Expand Up @@ -1754,16 +1771,17 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)

ResetMapDownload();

if(pMapSha256 && g_Config.m_ClHttpMapDownload)
if(pMapSha256)
{
char aUrl[256];
char aEscaped[256];
EscapeUrl(aEscaped, sizeof(aEscaped), m_aMapdownloadFilename + 15); // cut off downloadedmaps/
bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps2.ddnet.tw") != 0 || m_aMapDownloadUrl[0] == '\0';
bool UseConfigUrl = str_comp(g_Config.m_ClMapDownloadUrl, "https://maps.ddnet.org") != 0 || m_aMapDownloadUrl[0] == '\0';
str_format(aUrl, sizeof(aUrl), "%s/%s", UseConfigUrl ? g_Config.m_ClMapDownloadUrl : m_aMapDownloadUrl, aEscaped);

m_pMapdownloadTask = HttpGetFile(aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
m_pMapdownloadTask = HttpGetFile(pMapUrl ? pMapUrl : aUrl, Storage(), m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime});
m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB
Engine()->AddJob(m_pMapdownloadTask);
}
else
Expand Down Expand Up @@ -2886,16 +2904,12 @@ void CClient::Update()
{
if(m_pMapdownloadTask->State() == HTTP_DONE)
FinishMapDownload();
else if(m_pMapdownloadTask->State() == HTTP_ERROR)
else if(m_pMapdownloadTask->State() == HTTP_ERROR || m_pMapdownloadTask->State() == HTTP_ABORTED)
{
dbg_msg("webdl", "http failed, falling back to gameserver");
ResetMapDownload();
SendMapRequest();
}
else if(m_pMapdownloadTask->State() == HTTP_ABORTED)
{
m_pMapdownloadTask = NULL;
}
}

if(m_pDDNetInfoTask)
Expand Down Expand Up @@ -3193,7 +3207,9 @@ void CClient::Run()
// handle pending demo play
if(m_aCmdPlayDemo[0])
{
const char *pError = DemoPlayer_Play(m_aCmdPlayDemo, IStorage::TYPE_ABSOLUTE);
const char *pError = DemoPlayer_Play(m_aCmdPlayDemo, IStorage::TYPE_ALL);
if(pError && !fs_is_relative_path(m_aCmdPlayDemo))
pError = DemoPlayer_Play(m_aCmdPlayDemo, IStorage::TYPE_ABSOLUTE);
if(pError)
dbg_msg("demo_player", "playing passed demo file '%s' failed: %s", m_aCmdPlayDemo, pError);
m_aCmdPlayDemo[0] = 0;
Expand All @@ -3202,7 +3218,9 @@ void CClient::Run()
// handle pending map edits
if(m_aCmdEditMap[0])
{
int Result = m_pEditor->Load(m_aCmdEditMap, IStorage::TYPE_ABSOLUTE);
int Result = m_pEditor->Load(m_aCmdEditMap, IStorage::TYPE_ALL);
if(!Result && !fs_is_relative_path(m_aCmdEditMap))
m_pEditor->Load(m_aCmdEditMap, IStorage::TYPE_ABSOLUTE);
if(Result)
g_Config.m_ClEditor = true;
else
Expand Down Expand Up @@ -3497,7 +3515,7 @@ bool CClient::CtrlShiftKey(int Key, bool &Last)
void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData)
{
CClient *pSelf = (CClient *)pUserData;
str_copy(pSelf->m_aCmdConnect, pResult->GetString(0));
pSelf->HandleConnectLink(pResult->GetString(0));
}

void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData)
Expand Down Expand Up @@ -3903,7 +3921,10 @@ const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
if(pError)
{
if(!m_DemoPlayer.ExtractMap(Storage()))
{
DisconnectWithReason(pError);
return pError;
}

Sha = m_DemoPlayer.GetMapInfo()->m_Sha256;
pError = LoadMapSearch(pMapInfo->m_aName, &Sha, Crc);
Expand Down Expand Up @@ -3967,9 +3988,7 @@ const char *CClient::DemoPlayer_Render(const char *pFilename, int StorageType, c
void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData)
{
CClient *pSelf = (CClient *)pUserData;
const char *pError = pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL);
if(pError)
pSelf->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", pError);
pSelf->HandleDemoPath(pResult->GetString(0));
}

void CClient::Con_DemoPlay(IConsole::IResult *pResult, void *pUserData)
Expand Down Expand Up @@ -4140,7 +4159,7 @@ void CClient::InitChecksum()
}

#ifndef DDNET_CHECKSUM_SALT
// salt@checksum.ddnet.tw: db877f2b-2ddb-3ba6-9f67-a6d169ec671d
// salt@checksum.ddnet.org: db877f2b-2ddb-3ba6-9f67-a6d169ec671d
#define DDNET_CHECKSUM_SALT \
{ \
{ \
Expand Down Expand Up @@ -4523,7 +4542,10 @@ void CClient::HandleConnectAddress(const NETADDR *pAddr)

void CClient::HandleConnectLink(const char *pLink)
{
str_copy(m_aCmdConnect, pLink + sizeof(CONNECTLINK) - 1);
if(str_startswith(pLink, CONNECTLINK))
str_copy(m_aCmdConnect, pLink + sizeof(CONNECTLINK) - 1);
else
str_copy(m_aCmdConnect, pLink);
}

void CClient::HandleDemoPath(const char *pPath)
Expand All @@ -4536,6 +4558,27 @@ void CClient::HandleMapPath(const char *pPath)
str_copy(m_aCmdEditMap, pPath);
}

static bool UnknownArgumentCallback(const char *pCommand, void *pUser)
{
CClient *pClient = static_cast<CClient *>(pUser);
if(str_startswith(pCommand, CONNECTLINK))
{
pClient->HandleConnectLink(pCommand);
return true;
}
else if(str_endswith(pCommand, ".demo"))
{
pClient->HandleDemoPath(pCommand);
return true;
}
else if(str_endswith(pCommand, ".map"))
{
pClient->HandleMapPath(pCommand);
return true;
}
return false;
}

/*
Server Time
Client Mirror Time
Expand All @@ -4561,6 +4604,8 @@ int main(int argc, const char **argv)
{
#if defined(CONF_PLATFORM_ANDROID)
const char **argv = const_cast<const char **>(argv2);
#elif defined(CONF_FAMILY_WINDOWS)
CWindowsComLifecycle WindowsComLifecycle;
#endif
CCmdlineFix CmdlineFix(&argc, &argv);
bool Silent = false;
Expand Down Expand Up @@ -4719,14 +4764,9 @@ int main(int argc, const char **argv)
g_Config.m_ClConfigVersion = 1;

// parse the command line arguments
if(argc == 2 && str_startswith(argv[1], CONNECTLINK))
pClient->HandleConnectLink(argv[1]);
else if(argc == 2 && str_endswith(argv[1], ".demo"))
pClient->HandleDemoPath(argv[1]);
else if(argc == 2 && str_endswith(argv[1], ".map"))
pClient->HandleMapPath(argv[1]);
else if(argc > 1)
pConsole->ParseArguments(argc - 1, (const char **)&argv[1]);
pConsole->SetUnknownCommandCallback(UnknownArgumentCallback, pClient);
pConsole->ParseArguments(argc - 1, (const char **)&argv[1]);
pConsole->SetUnknownCommandCallback(IConsole::EmptyUnknownCommandCallback, nullptr);

if(pSteam->GetConnectAddress())
{
Expand Down Expand Up @@ -4823,7 +4863,7 @@ bool CClient::RaceRecord_IsRecording()
void CClient::RequestDDNetInfo()
{
char aUrl[256];
str_copy(aUrl, "https://info2.ddnet.tw/info");
str_copy(aUrl, "https://info.ddnet.org/info");

if(g_Config.m_BrIndicateFinished)
{
Expand Down
1 change: 1 addition & 0 deletions src/engine/client/client.h
Expand Up @@ -203,6 +203,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
char m_aMapDetailsName[256];
int m_MapDetailsCrc;
SHA256_DIGEST m_MapDetailsSha256;
char m_aMapDetailsUrl[256];

char m_aDDNetInfoTmp[64];
std::shared_ptr<CHttpRequest> m_pDDNetInfoTask;
Expand Down
1 change: 1 addition & 0 deletions src/engine/client/graphics_threaded.cpp
Expand Up @@ -849,6 +849,7 @@ bool CGraphics_Threaded::ScreenshotDirect()
void CGraphics_Threaded::TextureSet(CTextureHandle TextureID)
{
dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin");
dbg_assert(!TextureID.IsValid() || m_vTextureIndices[TextureID.Id()] == -1, "Texture handle was not invalid, but also did not correlate to an existing texture.");
m_State.m_Texture = TextureID.Id();
}

Expand Down
2 changes: 0 additions & 2 deletions src/engine/client/keynames.h
Expand Up @@ -4,8 +4,6 @@
#error do not include this header!
#endif

#include <string.h>

const char g_aaKeyStrings[512][20] = // NOLINT(misc-definitions-in-headers)
{
"unknown",
Expand Down
8 changes: 4 additions & 4 deletions src/engine/client/serverbrowser_http.cpp
Expand Up @@ -491,10 +491,10 @@ bool CServerBrowserHttp::Parse(json_value *pJson, std::vector<CServerInfo> *pvSe
}

static const char *DEFAULT_SERVERLIST_URLS[] = {
"https://master1.ddnet.tw/ddnet/15/servers.json",
"https://master2.ddnet.tw/ddnet/15/servers.json",
"https://master3.ddnet.tw/ddnet/15/servers.json",
"https://master4.ddnet.tw/ddnet/15/servers.json",
"https://master1.ddnet.org/ddnet/15/servers.json",
"https://master2.ddnet.org/ddnet/15/servers.json",
"https://master3.ddnet.org/ddnet/15/servers.json",
"https://master4.ddnet.org/ddnet/15/servers.json",
};

IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, const char *pPreviousBestUrl)
Expand Down
2 changes: 1 addition & 1 deletion src/engine/client/updater.cpp
Expand Up @@ -30,7 +30,7 @@ class CUpdaterFetchTask : public CHttpRequest

static const char *GetUpdaterUrl(char *pBuf, int BufSize, const char *pFile)
{
str_format(pBuf, BufSize, "https://update6.ddnet.tw/%s", pFile);
str_format(pBuf, BufSize, "https://update.ddnet.org/%s", pFile);
return pBuf;
}

Expand Down
3 changes: 3 additions & 0 deletions src/engine/console.h
Expand Up @@ -85,8 +85,10 @@ class IConsole : public IInterface
typedef void (*FPossibleCallback)(int Index, const char *pCmd, void *pUser);
typedef void (*FCommandCallback)(IResult *pResult, void *pUserData);
typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData);
typedef bool (*FUnknownCommandCallback)(const char *pCommand, void *pUser); // returns true if the callback has handled the argument

static void EmptyPossibleCommandCallback(int Index, const char *pCmd, void *pUser) {}
static bool EmptyUnknownCommandCallback(const char *pCommand, void *pUser) { return false; }

virtual void Init() = 0;
virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const = 0;
Expand All @@ -110,6 +112,7 @@ class IConsole : public IInterface
virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) = 0;
virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = gs_ConsoleDefaultColor) = 0;
virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) = 0;
virtual void SetUnknownCommandCallback(FUnknownCommandCallback pfnCallback, void *pUser) = 0;
virtual void InitChecksum(CChecksumData *pData) const = 0;

virtual void SetAccessLevel(int AccessLevel) = 0;
Expand Down
8 changes: 8 additions & 0 deletions src/engine/demo.h
Expand Up @@ -75,11 +75,19 @@ class IDemoPlayer : public IInterface
DEMOTYPE_SERVER,
};

enum ETickOffset
{
TICK_CURRENT = 1, // update the current tick again
TICK_PREVIOUS = 0, // go to the previous tick
TICK_NEXT = 3, // go to the next tick
};

~IDemoPlayer() {}
virtual void SetSpeed(float Speed) = 0;
virtual void SetSpeedIndex(int Offset) = 0;
virtual int SeekPercent(float Percent) = 0;
virtual int SeekTime(float Seconds) = 0;
virtual int SeekTick(ETickOffset TickOffset) = 0;
virtual int SetPos(int WantedTick) = 0;
virtual void Pause() = 0;
virtual void Unpause() = 0;
Expand Down
126 changes: 78 additions & 48 deletions src/engine/gfx/image_loader.cpp
@@ -1,27 +1,30 @@
#include "image_loader.h"
#include <base/log.h>
#include <base/system.h>
#include <csetjmp>
#include <cstdlib>

#include <png.h>

struct SLibPNGWarningItem
{
SImageByteBuffer *m_pByteLoader;
const char *pFileName;
const char *m_pFileName;
std::jmp_buf m_Buf;
};

static void LibPNGError(png_structp png_ptr, png_const_charp error_msg)
[[noreturn]] static void LibPNGError(png_structp png_ptr, png_const_charp error_msg)
{
SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
pUserStruct->m_pByteLoader->m_Err = -1;
dbg_msg("libpng", "error for file \"%s\": %s", pUserStruct->pFileName, error_msg);
dbg_msg("libpng", "error for file \"%s\": %s", pUserStruct->m_pFileName, error_msg);
std::longjmp(pUserStruct->m_Buf, 1);
}

static void LibPNGWarning(png_structp png_ptr, png_const_charp warning_msg)
{
SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
dbg_msg("libpng", "warning for file \"%s\": %s", pUserStruct->pFileName, warning_msg);
dbg_msg("libpng", "warning for file \"%s\": %s", pUserStruct->m_pFileName, warning_msg);
}

static bool FileMatchesImageType(SImageByteBuffer &ByteLoader)
Expand Down Expand Up @@ -75,7 +78,8 @@ static void LibPNGSetImageFormat(EImageFormat &ImageFormat, int LibPNGColorType)

static void LibPNGDeleteReadStruct(png_structp pPNGStruct, png_infop pPNGInfo)
{
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
if(pPNGInfo != nullptr)
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
png_destroy_read_struct(&pPNGStruct, NULL, NULL);
}

Expand Down Expand Up @@ -127,6 +131,15 @@ static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)

bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, int &Width, int &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat)
{
png_infop pPNGInfo = nullptr;
int ColorType;
png_byte BitDepth;
int ColorChannelCount;
int BytesInRow;
Height = 0;
png_bytepp pRowPointers = nullptr;
SLibPNGWarningItem UserErrorStruct = {&ByteLoader, pFileName, {}};

png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

if(pPNGStruct == NULL)
Expand All @@ -135,7 +148,22 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn
return false;
}

png_infop pPNGInfo = png_create_info_struct(pPNGStruct);
if(setjmp(UserErrorStruct.m_Buf))
{
if(pRowPointers != nullptr)
{
for(int i = 0; i < Height; ++i)
{
delete[] pRowPointers[i];
}
}
delete[] pRowPointers;
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
return false;
}
png_set_error_fn(pPNGStruct, &UserErrorStruct, LibPNGError, LibPNGWarning);

pPNGInfo = png_create_info_struct(pPNGStruct);

if(pPNGInfo == NULL)
{
Expand All @@ -144,9 +172,6 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn
return false;
}

SLibPNGWarningItem UserErrorStruct = {&ByteLoader, pFileName};
png_set_error_fn(pPNGStruct, &UserErrorStruct, LibPNGError, LibPNGWarning);

if(!FileMatchesImageType(ByteLoader))
{
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
Expand All @@ -169,77 +194,82 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn

Width = png_get_image_width(pPNGStruct, pPNGInfo);
Height = png_get_image_height(pPNGStruct, pPNGInfo);
int ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
png_byte BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
PngliteIncompatible = PngliteIncompatibility(pPNGStruct, pPNGInfo);

bool PNGErr = false;

if(BitDepth == 16)
{
png_set_strip_16(pPNGStruct);
}
else if(BitDepth > 8)
{
dbg_msg("png", "non supported bit depth.");
PNGErr = true;
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
return false;
}

if(Width == 0 || Height == 0 || BitDepth == 0)
{
dbg_msg("png", "image had width, height or bit depth of 0.");
PNGErr = true;
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
return false;
}

if(!PNGErr)
{
if(ColorType == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(pPNGStruct);
if(ColorType == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(pPNGStruct);

if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
png_set_expand_gray_1_2_4_to_8(pPNGStruct);
if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
png_set_expand_gray_1_2_4_to_8(pPNGStruct);

if(png_get_valid(pPNGStruct, pPNGInfo, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(pPNGStruct);
if(png_get_valid(pPNGStruct, pPNGInfo, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(pPNGStruct);

png_read_update_info(pPNGStruct, pPNGInfo);
png_read_update_info(pPNGStruct, pPNGInfo);

int ColorChannelCount = LibPNGGetColorChannelCount(ColorType);
int BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo);
ColorChannelCount = LibPNGGetColorChannelCount(ColorType);
BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo);

if(BytesInRow == Width * ColorChannelCount)
if(BytesInRow == Width * ColorChannelCount)
{
pRowPointers = new png_bytep[Height];
for(int y = 0; y < Height; ++y)
{
png_bytepp pRowPointers = new png_bytep[Height];
for(int y = 0; y < Height; ++y)
{
pRowPointers[y] = new png_byte[BytesInRow];
}
pRowPointers[y] = new png_byte[BytesInRow];
}

png_read_image(pPNGStruct, pRowPointers);
png_read_image(pPNGStruct, pRowPointers);

if(ByteLoader.m_Err == 0)
pImageBuff = (uint8_t *)malloc((size_t)Height * (size_t)Width * (size_t)ColorChannelCount * sizeof(uint8_t));
else
PNGErr = true;
if(ByteLoader.m_Err == 0)
pImageBuff = (uint8_t *)malloc((size_t)Height * (size_t)Width * (size_t)ColorChannelCount * sizeof(uint8_t));

for(int i = 0; i < Height; ++i)
{
if(ByteLoader.m_Err == 0)
mem_copy(&pImageBuff[i * BytesInRow], pRowPointers[i], BytesInRow);
delete[] pRowPointers[i];
}
delete[] pRowPointers;
for(int i = 0; i < Height; ++i)
{
if(ByteLoader.m_Err == 0)
mem_copy(&pImageBuff[i * BytesInRow], pRowPointers[i], BytesInRow);
delete[] pRowPointers[i];
}
delete[] pRowPointers;
pRowPointers = nullptr;

LibPNGSetImageFormat(ImageFormat, ColorType);
if(ByteLoader.m_Err != 0)
{
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
return false;
}
else
PNGErr = true;

LibPNGSetImageFormat(ImageFormat, ColorType);
}
else
{
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
return false;
}

png_destroy_info_struct(pPNGStruct, &pPNGInfo);
png_destroy_read_struct(&pPNGStruct, NULL, NULL);

return !PNGErr;
return true;
}

static void WriteDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite)
Expand Down
3 changes: 0 additions & 3 deletions src/engine/server.h
Expand Up @@ -229,7 +229,6 @@ class IServer : public IInterface
virtual void ChangeMap(const char *pMap) = 0;

virtual void DemoRecorder_HandleAutoStart() = 0;
virtual bool DemoRecorder_IsRecording() = 0;

// DDRace

Expand Down Expand Up @@ -270,8 +269,6 @@ class IGameServer : public IInterface
virtual void OnInit() = 0;
virtual void OnConsoleInit() = 0;
virtual void OnMapChange(char *pNewMapName, int MapNameSize) = 0;

// FullShutdown is true if the program is about to exit (not if the map is changed)
virtual void OnShutdown() = 0;

virtual void OnTick() = 0;
Expand Down
1 change: 1 addition & 0 deletions src/engine/server/databases/connection.h
Expand Up @@ -108,6 +108,7 @@ std::unique_ptr<IDbConnection> CreateMysqlConnection(
const char *pUser,
const char *pPass,
const char *pIp,
const char *pBindaddr,
int Port,
bool Setup);

Expand Down
14 changes: 12 additions & 2 deletions src/engine/server/databases/mysql.cpp
Expand Up @@ -64,6 +64,7 @@ class CMysqlConnection : public IDbConnection
const char *pUser,
const char *pPass,
const char *pIp,
const char *pBindaddr,
int Port,
bool Setup);
~CMysqlConnection();
Expand Down Expand Up @@ -138,6 +139,7 @@ class CMysqlConnection : public IDbConnection
char m_aUser[64];
char m_aPass[64];
char m_aIp[64];
char m_aBindaddr[128];
int m_Port;
bool m_Setup;

Expand All @@ -155,6 +157,7 @@ CMysqlConnection::CMysqlConnection(
const char *pUser,
const char *pPass,
const char *pIp,
const char *pBindaddr,
int Port,
bool Setup) :
IDbConnection(pPrefix),
Expand All @@ -173,6 +176,7 @@ CMysqlConnection::CMysqlConnection(
str_copy(m_aUser, pUser, sizeof(m_aUser));
str_copy(m_aPass, pPass, sizeof(m_aPass));
str_copy(m_aIp, pIp, sizeof(m_aIp));
str_copy(m_aBindaddr, pBindaddr, sizeof(m_aBindaddr));
}

CMysqlConnection::~CMysqlConnection()
Expand Down Expand Up @@ -217,7 +221,7 @@ void CMysqlConnection::Print(IConsole *pConsole, const char *pMode)

CMysqlConnection *CMysqlConnection::Copy()
{
return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup);
return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_aBindaddr, m_Port, m_Setup);
}

void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
Expand Down Expand Up @@ -273,6 +277,10 @@ bool CMysqlConnection::ConnectImpl()
mysql_options(&m_Mysql, MYSQL_OPT_WRITE_TIMEOUT, &OptWriteTimeout);
mysql_options(&m_Mysql, MYSQL_OPT_RECONNECT, &OptReconnect);
mysql_options(&m_Mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");
if(m_aBindaddr[0] != '\0')
{
mysql_options(&m_Mysql, MYSQL_OPT_BIND, m_aBindaddr);
}

if(!mysql_real_connect(&m_Mysql, m_aIp, m_aUser, m_aPass, nullptr, m_Port, nullptr, CLIENT_IGNORE_SIGPIPE))
{
Expand Down Expand Up @@ -711,10 +719,11 @@ std::unique_ptr<IDbConnection> CreateMysqlConnection(
const char *pUser,
const char *pPass,
const char *pIp,
const char *pBindaddr,
int Port,
bool Setup)
{
return std::make_unique<CMysqlConnection>(pDatabase, pPrefix, pUser, pPass, pIp, Port, Setup);
return std::make_unique<CMysqlConnection>(pDatabase, pPrefix, pUser, pPass, pIp, pBindaddr, Port, Setup);
}
#else
int MysqlInit()
Expand All @@ -730,6 +739,7 @@ std::unique_ptr<IDbConnection> CreateMysqlConnection(
const char *pUser,
const char *pPass,
const char *pIp,
const char *pBindaddr,
int Port,
bool Setup)
{
Expand Down