Browse Source

Support unencrypted multiplayer games with no password

pull/3245/head
staphen 4 years ago committed by Anders Jenbo
parent
commit
ffbbcc6d62
  1. 45
      CMakeLists.txt
  2. 4
      Source/DiabloUI/diabloui.cpp
  3. 61
      Source/DiabloUI/selgame.cpp
  4. 2
      Source/DiabloUI/selhero.cpp
  5. 4
      Source/DiabloUI/ui_item.h
  6. 9
      Source/automap.cpp
  7. 8
      Source/dvlnet/abstract_net.h
  8. 5
      Source/dvlnet/base.cpp
  9. 5
      Source/dvlnet/base.h
  10. 14
      Source/dvlnet/base_protocol.h
  11. 27
      Source/dvlnet/cdwrap.h
  12. 4
      Source/dvlnet/loopback.cpp
  13. 4
      Source/dvlnet/loopback.h
  14. 198
      Source/dvlnet/packet.cpp
  15. 31
      Source/dvlnet/packet.h
  16. 13
      Source/dvlnet/tcp_client.cpp
  17. 4
      Source/dvlnet/tcp_client.h
  18. 4
      Source/dvlnet/tcp_server.cpp
  19. 4
      Source/dvlnet/tcp_server.h
  20. 2
      Source/multi.cpp
  21. 1
      Source/multi.h
  22. 2
      Source/storm/storm.h
  23. 27
      Source/storm/storm_net.cpp
  24. 5
      vcpkg.json

45
CMakeLists.txt

@ -27,15 +27,13 @@ option(BINARY_RELEASE "Enable options for binary release" OFF)
option(NIGHTLY_BUILD "Enable options for nightly build" OFF)
option(USE_SDL1 "Use SDL1.2 instead of SDL2" OFF)
option(NONET "Disable network support" OFF)
cmake_dependent_option(DISABLE_TCP "Disable TCP multiplayer option" OFF "NOT NONET" ON)
cmake_dependent_option(DISABLE_ZERO_TIER "Disable ZeroTier multiplayer option" OFF "NOT NONET" ON)
cmake_dependent_option(PACKET_ENCRYPTION "Encrypt network packets" ON "NOT NONET" OFF)
option(NOSOUND "Disable sound support" OFF)
option(RUN_TESTS "Build and run tests" OFF)
option(ENABLE_CODECOVERAGE "Instrument code for code coverage (only enabled with RUN_TESTS)" OFF)
if(NOT NONET)
option(DISABLE_TCP "Disable TCP multiplayer option" OFF)
option(DISABLE_ZERO_TIER "Disable ZeroTier multiplayer option" OFF)
endif()
option(DISABLE_STREAMING_MUSIC "Disable streaming music (to work around broken platform implementations)" OFF)
mark_as_advanced(DISABLE_STREAMING_MUSIC)
option(DISABLE_STREAMING_SOUNDS "Disable streaming sounds (to work around broken platform implementations)" OFF)
@ -65,6 +63,9 @@ if(NIGHTLY_BUILD OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(CPACK ON)
endif()
if(PACKET_ENCRYPTION)
list(APPEND VCPKG_MANIFEST_FEATURES "encryption")
endif()
if(USE_GETTEXT_FROM_VCPKG)
list(APPEND VCPKG_MANIFEST_FEATURES "translations")
endif()
@ -78,7 +79,7 @@ if(NOT NOSOUND)
"DEVILUTIONX_SYSTEM_SDL_AUDIOLIB AND NOT DIST" ON)
endif()
if(NOT NONET)
if(PACKET_ENCRYPTION)
option(DEVILUTIONX_SYSTEM_LIBSODIUM "Use system-provided libsodium" ON)
cmake_dependent_option(DEVILUTIONX_STATIC_LIBSODIUM "Link static libsodium" OFF
"DEVILUTIONX_SYSTEM_LIBSODIUM AND NOT DIST" ON)
@ -216,6 +217,13 @@ if(PIE)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
endif()
if(NONET)
# Fix dependent options if platform defs disable network
set(DISABLE_TCP ON)
set(DISABLE_ZERO_TIER ON)
set(PACKET_ENCRYPTION OFF)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -227,7 +235,7 @@ if(NOT NINTENDO_3DS)
find_package(Threads REQUIRED)
endif()
if(NOT NONET)
if(PACKET_ENCRYPTION)
if(DEVILUTIONX_SYSTEM_LIBSODIUM)
set(sodium_USE_STATIC_LIBS ${DEVILUTIONX_STATIC_LIBSODIUM})
find_package(sodium REQUIRED)
@ -531,18 +539,26 @@ if(NINTENDO_SWITCH)
Source/platform/switch/network.cpp
Source/platform/switch/keyboard.cpp
Source/platform/switch/docking.cpp
Source/platform/switch/random.cpp
Source/platform/switch/asio/pause.c
Source/platform/switch/asio/net/if.c
Source/platform/switch/asio/sys/signal.c)
if(PACKET_ENCRYPTION)
list(APPEND libdevilutionx_SRCS
Source/platform/switch/random.cpp)
endif()
endif()
if(VITA)
list(APPEND libdevilutionx_SRCS
Source/platform/vita/random.cpp
Source/platform/vita/network.cpp
Source/platform/vita/keyboard.cpp
Source/platform/vita/touch.cpp)
if(PACKET_ENCRYPTION)
list(APPEND libdevilutionx_SRCS
Source/platform/vita/random.cpp)
endif()
endif()
if(NINTENDO_3DS)
@ -551,13 +567,17 @@ if(NINTENDO_3DS)
Source/platform/ctr/keyboard.cpp
Source/platform/ctr/display.cpp
Source/platform/ctr/messagebox.cpp
Source/platform/ctr/random.cpp
Source/platform/ctr/sockets.cpp
Source/platform/ctr/locale.cpp
Source/platform/ctr/asio/net/if.c
Source/platform/ctr/asio/sys/socket.c
Source/platform/ctr/asio/sys/uio.c)
set(BIN_TARGET ${BIN_TARGET}.elf)
if(PACKET_ENCRYPTION)
list(APPEND libdevilutionx_SRCS
Source/platform/ctr/random.cpp)
endif()
endif()
if(RUN_TESTS)
@ -847,7 +867,9 @@ if(NOT NONET)
if(NOT DISABLE_TCP)
target_link_libraries(libdevilutionx PUBLIC asio)
endif()
target_link_libraries(libdevilutionx PUBLIC sodium)
if(PACKET_ENCRYPTION)
target_link_libraries(libdevilutionx PUBLIC sodium)
endif()
endif()
target_link_libraries(libdevilutionx PUBLIC fmt::fmt)
@ -874,6 +896,7 @@ foreach(
GPERF_HEAP_MAIN
GPERF_HEAP_FIRST_GAME_ITERATION
STREAM_ALL_AUDIO
PACKET_ENCRYPTION
VIRTUAL_GAMEPAD
)
if(${def_name})

4
Source/DiabloUI/diabloui.cpp

@ -64,6 +64,7 @@ bool UiItemsWraps;
char *UiTextInput;
int UiTextInputLen;
bool textInputActive = true;
bool allowEmptyTextInput = false;
std::size_t SelectedItem = 0;
@ -111,6 +112,7 @@ void UiInitList(int count, void (*fnFocus)(int value), void (*fnSelect)(int valu
auto *pItemUIEdit = static_cast<UiEdit *>(item.get());
SDL_SetTextInputRect(&item->m_rect);
textInputActive = true;
allowEmptyTextInput = pItemUIEdit->m_allowEmpty;
#ifdef __SWITCH__
switch_start_text_input(pItemUIEdit->m_hint, pItemUIEdit->m_value, pItemUIEdit->m_max_length, /*multiline=*/0);
#elif defined(__vita__)
@ -425,7 +427,7 @@ void UiFocusNavigationSelect()
{
UiPlaySelectSound();
if (textInputActive) {
if (strlen(UiTextInput) == 0) {
if (!allowEmptyTextInput && strlen(UiTextInput) == 0) {
return;
}
#ifndef __SWITCH__

61
Source/DiabloUI/selgame.cpp

@ -41,8 +41,6 @@ std::vector<std::unique_ptr<UiItemBase>> vecSelGameDialog;
std::vector<std::string> Gamelist;
int HighlightedItem;
constexpr const char *DefaultPassword = "asd";
} // namespace
void selgame_FreeVectors()
@ -89,11 +87,14 @@ void selgame_GameSelection_Init()
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 300), (Sint16)(UI_OFFSET_Y + 211), 295, 33 };
vecSelGameDialog.push_back(std::make_unique<UiArtText>(_("Select Action"), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3));
#ifdef PACKET_ENCRYPTION
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(_("Create Game"), 0));
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(_("Join Game"), 1));
#endif
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(_("Create Public Game"), 1));
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(_("Join Game"), 2));
for (unsigned i = 0; i < Gamelist.size(); i++) {
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(Gamelist[i].c_str(), i + 2));
vecSelGameDlgItems.push_back(std::make_unique<UiListItem>(Gamelist[i].c_str(), i + 3));
}
vecSelGameDialog.push_back(std::make_unique<UiList>(vecSelGameDlgItems, PANEL_LEFT + 305, (UI_OFFSET_Y + 255), 285, 26, UiFlags::AlignCenter | UiFlags::FontSize24 | UiFlags::ColorUiGold));
@ -104,7 +105,13 @@ void selgame_GameSelection_Init()
SDL_Rect rect6 = { (Sint16)(PANEL_LEFT + 449), (Sint16)(UI_OFFSET_Y + 427), 140, 35 };
vecSelGameDialog.push_back(std::make_unique<UiArtTextButton>(_("CANCEL"), &UiFocusNavigationEsc, rect6, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));
UiInitList(vecSelGameDlgItems.size(), selgame_GameSelection_Focus, selgame_GameSelection_Select, selgame_GameSelection_Esc, vecSelGameDialog, true, nullptr, HighlightedItem);
auto selectFn = [](int index) {
// UiListItem::m_value could be different from
// the index if packet encryption is disabled
int itemValue = vecSelGameDlgItems[index]->m_value;
selgame_GameSelection_Select(itemValue);
};
UiInitList(vecSelGameDlgItems.size(), selgame_GameSelection_Focus, selectFn, selgame_GameSelection_Esc, vecSelGameDialog, true, nullptr, HighlightedItem);
}
void selgame_GameSelection_Focus(int value)
@ -115,6 +122,9 @@ void selgame_GameSelection_Focus(int value)
strncpy(selgame_Description, _("Create a new game with a difficulty setting of your choice."), sizeof(selgame_Description) - 1);
break;
case 1:
strncpy(selgame_Description, _("Create a new public game that anyone can join with a difficulty setting of your choice."), sizeof(selgame_Description) - 1);
break;
case 2:
strncpy(selgame_Description, _("Enter an IP or a hostname and join a game already in progress at that address."), sizeof(selgame_Description) - 1);
break;
default:
@ -143,9 +153,8 @@ void selgame_GameSelection_Select(int value)
selgame_enteringGame = true;
selgame_selectedGame = value;
if (value > 1 && selgame_selectedGame != 0) {
strcpy(selgame_Ip, Gamelist[value - 2].c_str());
strcpy(selgame_Password, DefaultPassword);
if (value > 2) {
strcpy(selgame_Ip, Gamelist[value - 3].c_str());
selgame_Password_Select(value);
return;
}
@ -167,7 +176,8 @@ void selgame_GameSelection_Select(int value)
vecSelGameDialog.push_back(std::make_unique<UiArtText>(selgame_Description, rect3, UiFlags::FontSize12 | UiFlags::ColorUiSilverDark, 1, 16));
switch (value) {
case 0: {
case 0:
case 1: {
title = _("Create Game");
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 299), (Sint16)(UI_OFFSET_Y + 211), 295, 35 };
@ -188,14 +198,14 @@ void selgame_GameSelection_Select(int value)
UiInitList(vecSelGameDlgItems.size(), selgame_Diff_Focus, selgame_Diff_Select, selgame_Diff_Esc, vecSelGameDialog, true);
break;
}
case 1: {
case 2: {
title = _("Join TCP Games");
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 305), (Sint16)(UI_OFFSET_Y + 211), 285, 33 };
vecSelGameDialog.push_back(std::make_unique<UiArtText>(_("Enter address"), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3));
SDL_Rect rect5 = { (Sint16)(PANEL_LEFT + 305), (Sint16)(UI_OFFSET_Y + 314), 285, 33 };
vecSelGameDialog.push_back(std::make_unique<UiEdit>(_("Enter address"), selgame_Ip, 128, rect5, UiFlags::FontSize24 | UiFlags::ColorUiGold));
vecSelGameDialog.push_back(std::make_unique<UiEdit>(_("Enter address"), selgame_Ip, 128, false, rect5, UiFlags::FontSize24 | UiFlags::ColorUiGold));
SDL_Rect rect6 = { (Sint16)(PANEL_LEFT + 299), (Sint16)(UI_OFFSET_Y + 427), 140, 35 };
vecSelGameDialog.push_back(std::make_unique<UiArtTextButton>(_("OK"), &UiFocusNavigationSelect, rect6, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));
@ -204,7 +214,12 @@ void selgame_GameSelection_Select(int value)
vecSelGameDialog.push_back(std::make_unique<UiArtTextButton>(_("CANCEL"), &UiFocusNavigationEsc, rect7, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));
HighlightedItem = 0;
#ifdef PACKET_ENCRYPTION
UiInitList(0, nullptr, selgame_Password_Init, selgame_GameSelection_Init, vecSelGameDialog);
#else
UiInitList(0, nullptr, selgame_Password_Select, selgame_GameSelection_Init, vecSelGameDialog);
#endif
break;
}
}
@ -375,7 +390,7 @@ void selgame_Speed_Select(int value)
{
nTickRate = vecSelGameDlgItems[value]->m_value;
if (provider == SELCONN_LOOPBACK) {
if (provider == SELCONN_LOOPBACK || selgame_selectedGame == 1) {
selgame_Password_Select(0);
return;
}
@ -404,8 +419,10 @@ void selgame_Password_Init(int /*value*/)
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 305), (Sint16)(UI_OFFSET_Y + 211), 285, 33 };
vecSelGameDialog.push_back(std::make_unique<UiArtText>(_("Enter Password"), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3));
// Allow password to be empty only when joining games
bool allowEmpty = selgame_selectedGame == 2;
SDL_Rect rect5 = { (Sint16)(PANEL_LEFT + 305), (Sint16)(UI_OFFSET_Y + 314), 285, 33 };
vecSelGameDialog.push_back(std::make_unique<UiEdit>(_("Enter Password"), selgame_Password, 15, rect5, UiFlags::FontSize24 | UiFlags::ColorUiGold));
vecSelGameDialog.push_back(std::make_unique<UiEdit>(_("Enter Password"), selgame_Password, 15, allowEmpty, rect5, UiFlags::FontSize24 | UiFlags::ColorUiGold));
SDL_Rect rect6 = { (Sint16)(PANEL_LEFT + 299), (Sint16)(UI_OFFSET_Y + 427), 140, 35 };
vecSelGameDialog.push_back(std::make_unique<UiArtTextButton>(_("OK"), &UiFocusNavigationSelect, rect6, UiFlags::AlignCenter | UiFlags::VerticalCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));
@ -443,9 +460,15 @@ static bool IsGameCompatible(const GameData &data)
void selgame_Password_Select(int /*value*/)
{
if (selgame_selectedGame != 0) {
char *gamePassword = nullptr;
if (selgame_selectedGame == 0)
gamePassword = selgame_Password;
if (selgame_selectedGame == 2 && strlen(selgame_Password) > 0)
gamePassword = selgame_Password;
if (selgame_selectedGame > 1) {
strcpy(sgOptions.Network.szPreviousHost, selgame_Ip);
if (SNetJoinGame(selgame_Ip, selgame_Password, gdwPlayerId)) {
if (SNetJoinGame(selgame_Ip, gamePassword, gdwPlayerId)) {
if (!IsGameCompatible(*m_game_data)) {
selgame_GameSelection_Select(1);
return;
@ -468,7 +491,7 @@ void selgame_Password_Select(int /*value*/)
m_game_data->bTheoQuest = sgOptions.Gameplay.bTheoQuest ? 1 : 0;
m_game_data->bCowQuest = sgOptions.Gameplay.bCowQuest ? 1 : 0;
if (SNetCreateGame(nullptr, selgame_Password, (char *)m_game_data, sizeof(*m_game_data), gdwPlayerId)) {
if (SNetCreateGame(nullptr, gamePassword, (char *)m_game_data, sizeof(*m_game_data), gdwPlayerId)) {
UiInitList_clear();
selgame_endMenu = true;
} else {
@ -481,8 +504,8 @@ void selgame_Password_Select(int /*value*/)
void selgame_Password_Esc()
{
if (selgame_selectedGame == 1)
selgame_GameSelection_Select(1);
if (selgame_selectedGame == 2)
selgame_GameSelection_Select(2);
else
selgame_GameSpeedSelection();
}
@ -524,7 +547,7 @@ bool UiSelectGame(GameData *gameData, int *playerId)
selgame_endMenu = false;
DvlNet_SetPassword(DefaultPassword);
DvlNet_ClearPassword();
DvlNet_ClearGamelist();
while (!selgame_endMenu) {

2
Source/DiabloUI/selhero.cpp

@ -275,7 +275,7 @@ void SelheroClassSelectorSelect(int value)
vecSelDlgItems.push_back(std::make_unique<UiArtText>(_("Enter Name"), rect1, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3));
SDL_Rect rect2 = { (Sint16)(PANEL_LEFT + 265), (Sint16)(UI_OFFSET_Y + 317), 320, 33 };
vecSelDlgItems.push_back(std::make_unique<UiEdit>(_("Enter Name"), selhero_heroInfo.name, 15, rect2, UiFlags::FontSize24 | UiFlags::ColorUiGold));
vecSelDlgItems.push_back(std::make_unique<UiEdit>(_("Enter Name"), selhero_heroInfo.name, 15, false, rect2, UiFlags::FontSize24 | UiFlags::ColorUiGold));
SDL_Rect rect3 = { (Sint16)(PANEL_LEFT + 279), (Sint16)(UI_OFFSET_Y + 429), 140, 35 };
vecSelDlgItems.push_back(std::make_unique<UiArtTextButton>(_("OK"), &UiFocusNavigationSelect, rect3, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold));

4
Source/DiabloUI/ui_item.h

@ -224,11 +224,12 @@ public:
class UiEdit : public UiItemBase {
public:
UiEdit(const char *hint, char *value, std::size_t max_length, SDL_Rect rect, UiFlags flags = UiFlags::None)
UiEdit(const char *hint, char *value, std::size_t max_length, bool allowEmpty, SDL_Rect rect, UiFlags flags = UiFlags::None)
: UiItemBase(UiType::Edit, rect, flags)
, m_hint(hint)
, m_value(value)
, m_max_length(max_length)
, m_allowEmpty(allowEmpty)
{
}
@ -236,6 +237,7 @@ public:
const char *m_hint;
char *m_value;
std::size_t m_max_length;
bool m_allowEmpty;
};
//=============================================================================

9
Source/automap.cpp

@ -466,11 +466,12 @@ void DrawAutomapText(const Surface &out)
linePosition.y += 15;
}
if (szPlayerDescript[0] != '\0') {
if (!PublicGame)
strcat(strcpy(desc, _("password: ")), szPlayerDescript);
DrawString(out, desc, linePosition);
linePosition.y += 15;
}
else
strcpy(desc, _("Public Game"));
DrawString(out, desc, linePosition);
linePosition.y += 15;
}
if (setlevel) {

8
Source/dvlnet/abstract_net.h

@ -22,8 +22,8 @@ public:
class abstract_net {
public:
virtual int create(std::string addrstr, std::string passwd) = 0;
virtual int join(std::string addrstr, std::string passwd) = 0;
virtual int create(std::string addrstr) = 0;
virtual int join(std::string addrstr) = 0;
virtual bool SNetReceiveMessage(int *sender, void **data, uint32_t *size) = 0;
virtual bool SNetSendMessage(int dest, void *data, unsigned int size) = 0;
virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status) = 0;
@ -44,6 +44,10 @@ public:
{
}
virtual void clear_password()
{
}
virtual void send_info_request()
{
}

5
Source/dvlnet/base.cpp

@ -17,6 +17,11 @@ void base::setup_password(std::string pw)
pktfty = std::make_unique<packet_factory>(pw);
}
void base::clear_password()
{
pktfty = std::make_unique<packet_factory>();
}
void base::RunEventHandler(_SNETEVENT &ev)
{
auto f = registered_handlers[static_cast<event_type>(ev.eventid)];

5
Source/dvlnet/base.h

@ -14,8 +14,8 @@ namespace net {
class base : public abstract_net {
public:
virtual int create(std::string addrstr, std::string passwd) = 0;
virtual int join(std::string addrstr, std::string passwd) = 0;
virtual int create(std::string addrstr) = 0;
virtual int join(std::string addrstr) = 0;
virtual bool SNetReceiveMessage(int *sender, void **data, uint32_t *size);
virtual bool SNetSendMessage(int playerId, void *data, unsigned int size);
@ -37,6 +37,7 @@ public:
void setup_gameinfo(buffer_t info);
virtual void setup_password(std::string pw);
virtual void clear_password();
virtual ~base() = default;

14
Source/dvlnet/base_protocol.h

@ -16,8 +16,8 @@ namespace net {
template <class P>
class base_protocol : public base {
public:
virtual int create(std::string addrstr, std::string passwd);
virtual int join(std::string addrstr, std::string passwd);
virtual int create(std::string addrstr);
virtual int join(std::string addrstr);
virtual void poll();
virtual void send(packet &pkt);
virtual void DisconnectNet(plr_t plr);
@ -108,8 +108,7 @@ void base_protocol<P>::send_info_request()
template <class P>
void base_protocol<P>::wait_join()
{
randombytes_buf(reinterpret_cast<unsigned char *>(&cookie_self),
sizeof(cookie_t));
cookie_self = packet_out::GenerateCookie();
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(PLR_BROADCAST,
PLR_MASTER, cookie_self, game_init_info);
proto.send(firstpeer, pkt->Data());
@ -122,9 +121,8 @@ void base_protocol<P>::wait_join()
}
template <class P>
int base_protocol<P>::create(std::string addrstr, std::string passwd)
int base_protocol<P>::create(std::string addrstr)
{
setup_password(passwd);
gamename = addrstr;
if (wait_network()) {
@ -136,10 +134,8 @@ int base_protocol<P>::create(std::string addrstr, std::string passwd)
}
template <class P>
int base_protocol<P>::join(std::string addrstr, std::string passwd)
int base_protocol<P>::join(std::string addrstr)
{
//addrstr = "fd80:56c2:e21c:0:199:931d:b14:c4d2";
setup_password(passwd);
gamename = addrstr;
if (wait_network())
if (wait_firstpeer())

27
Source/dvlnet/cdwrap.h

@ -17,12 +17,13 @@ private:
std::unique_ptr<abstract_net> dvlnet_wrap;
std::map<event_type, SEVTHANDLER> registered_handlers;
buffer_t game_init_info;
std::optional<std::string> game_pw;
void reset();
public:
virtual int create(std::string addrstr, std::string passwd);
virtual int join(std::string addrstr, std::string passwd);
virtual int create(std::string addrstr);
virtual int join(std::string addrstr);
virtual bool SNetReceiveMessage(int *sender, void **data, uint32_t *size);
virtual bool SNetSendMessage(int dest, void *data, unsigned int size);
virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status);
@ -41,6 +42,7 @@ public:
virtual void clear_gamelist();
virtual std::vector<std::string> get_gamelist();
virtual void setup_password(std::string pw);
virtual void clear_password();
cdwrap();
virtual ~cdwrap() = default;
@ -58,23 +60,28 @@ void cdwrap<T>::reset()
dvlnet_wrap.reset(new T);
dvlnet_wrap->setup_gameinfo(game_init_info);
if (game_pw != std::nullopt)
dvlnet_wrap->setup_password(*game_pw);
else
dvlnet_wrap->clear_password();
for (const auto &pair : registered_handlers)
dvlnet_wrap->SNetRegisterEventHandler(pair.first, pair.second);
}
template <class T>
int cdwrap<T>::create(std::string addrstr, std::string passwd)
int cdwrap<T>::create(std::string addrstr)
{
reset();
return dvlnet_wrap->create(addrstr, passwd);
return dvlnet_wrap->create(addrstr);
}
template <class T>
int cdwrap<T>::join(std::string addrstr, std::string passwd)
int cdwrap<T>::join(std::string addrstr)
{
game_init_info = buffer_t();
reset();
return dvlnet_wrap->join(addrstr, passwd);
return dvlnet_wrap->join(addrstr);
}
template <class T>
@ -186,8 +193,16 @@ std::vector<std::string> cdwrap<T>::get_gamelist()
template <class T>
void cdwrap<T>::setup_password(std::string pw)
{
game_pw = pw;
return dvlnet_wrap->setup_password(pw);
}
template <class T>
void cdwrap<T>::clear_password()
{
game_pw = std::nullopt;
return dvlnet_wrap->clear_password();
}
} // namespace net
} // namespace devilution

4
Source/dvlnet/loopback.cpp

@ -5,12 +5,12 @@
namespace devilution {
namespace net {
int loopback::create(std::string /*addrstr*/, std::string /*passwd*/)
int loopback::create(std::string /*addrstr*/)
{
return plr_single;
}
int loopback::join(std::string /*addrstr*/, std::string /*passwd*/)
int loopback::join(std::string /*addrstr*/)
{
ABORT();
}

4
Source/dvlnet/loopback.h

@ -20,8 +20,8 @@ public:
plr_single = 0;
};
virtual int create(std::string addrstr, std::string passwd);
virtual int join(std::string addrstr, std::string passwd);
virtual int create(std::string addrstr);
virtual int join(std::string addrstr);
virtual bool SNetReceiveMessage(int *sender, void **data, uint32_t *size);
virtual bool SNetSendMessage(int dest, void *data, unsigned int size);
virtual bool SNetReceiveTurns(char **data, size_t *size, uint32_t *status);

198
Source/dvlnet/packet.cpp

@ -1,10 +1,52 @@
#ifdef PACKET_ENCRYPTION
#include <sodium.h>
#else
#include <chrono>
#include <random>
#endif
#include "dvlnet/packet.h"
namespace devilution {
namespace net {
#ifndef NONET
static constexpr bool DisableEncryption = false;
#ifdef PACKET_ENCRYPTION
cookie_t packet_out::GenerateCookie()
{
cookie_t cookie;
randombytes_buf(reinterpret_cast<unsigned char *>(&cookie),
sizeof(cookie_t));
return cookie;
}
#else
class cookie_generator {
public:
cookie_generator()
{
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
generator.seed(seed);
}
cookie_t NewCookie()
{
return distribution(generator);
}
private:
std::default_random_engine generator;
std::uniform_int_distribution<cookie_t> distribution;
};
cookie_generator CookieGenerator;
cookie_t packet_out::GenerateCookie()
{
return CookieGenerator.NewCookie();
}
#endif
const char *packet_type_to_string(uint8_t packetType)
@ -66,158 +108,153 @@ void CheckPacketTypeOneOf(std::initializer_list<packet_type> expectedTypes, std:
const buffer_t &packet::Data()
{
if (!have_decrypted || !have_encrypted)
ABORT();
return encrypted_buffer;
assert(have_encrypted || have_decrypted);
if (have_encrypted)
return encrypted_buffer;
return decrypted_buffer;
}
packet_type packet::Type()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
return m_type;
}
plr_t packet::Source() const
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
return m_src;
}
plr_t packet::Destination() const
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
return m_dest;
}
const buffer_t &packet::Message()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
CheckPacketTypeOneOf({ PT_MESSAGE }, m_type);
return m_message;
}
turn_t packet::Turn()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
CheckPacketTypeOneOf({ PT_TURN }, m_type);
return m_turn;
}
cookie_t packet::Cookie()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT }, m_type);
return m_cookie;
}
plr_t packet::NewPlayer()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
CheckPacketTypeOneOf({ PT_JOIN_ACCEPT, PT_CONNECT, PT_DISCONNECT }, m_type);
return m_newplr;
}
const buffer_t &packet::Info()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
CheckPacketTypeOneOf({ PT_JOIN_REQUEST, PT_JOIN_ACCEPT, PT_CONNECT, PT_INFO_REPLY }, m_type);
return m_info;
}
leaveinfo_t packet::LeaveInfo()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
CheckPacketTypeOneOf({ PT_DISCONNECT }, m_type);
return m_leaveinfo;
}
void packet_in::Create(buffer_t buf)
{
if (have_encrypted || have_decrypted)
ABORT();
encrypted_buffer = std::move(buf);
assert(!have_encrypted && !have_decrypted);
if (buf.size() < sizeof(packet_type) + 2 * sizeof(plr_t))
throw packet_exception();
decrypted_buffer = std::move(buf);
have_decrypted = true;
// TCP server implementation forwards the original data to clients
// so although we are not decrypting anything,
// we save a copy in encrypted_buffer anyway
encrypted_buffer = decrypted_buffer;
have_encrypted = true;
}
void packet_in::Decrypt()
#ifdef PACKET_ENCRYPTION
void packet_in::Decrypt(buffer_t buf)
{
if (!have_encrypted)
ABORT();
if (have_decrypted)
return;
#ifndef NONET
if (!DisableEncryption) {
if (encrypted_buffer.size() < crypto_secretbox_NONCEBYTES
+ crypto_secretbox_MACBYTES
+ sizeof(packet_type) + 2 * sizeof(plr_t))
throw packet_exception();
auto pktlen = (encrypted_buffer.size()
- crypto_secretbox_NONCEBYTES
- crypto_secretbox_MACBYTES);
decrypted_buffer.resize(pktlen);
int status = crypto_secretbox_open_easy(
decrypted_buffer.data(),
encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
encrypted_buffer.size() - crypto_secretbox_NONCEBYTES,
encrypted_buffer.data(),
key.data());
if (status != 0)
throw packet_exception();
} else
#endif
{
if (encrypted_buffer.size() < sizeof(packet_type) + 2 * sizeof(plr_t))
throw packet_exception();
decrypted_buffer = encrypted_buffer;
}
assert(!have_encrypted && !have_decrypted);
encrypted_buffer = std::move(buf);
have_encrypted = true;
process_data();
if (encrypted_buffer.size() < crypto_secretbox_NONCEBYTES
+ crypto_secretbox_MACBYTES
+ sizeof(packet_type) + 2 * sizeof(plr_t))
throw packet_exception();
auto pktlen = (encrypted_buffer.size()
- crypto_secretbox_NONCEBYTES
- crypto_secretbox_MACBYTES);
decrypted_buffer.resize(pktlen);
int status = crypto_secretbox_open_easy(
decrypted_buffer.data(),
encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
encrypted_buffer.size() - crypto_secretbox_NONCEBYTES,
encrypted_buffer.data(),
key.data());
if (status != 0)
throw packet_exception();
have_decrypted = true;
}
#endif
#ifdef PACKET_ENCRYPTION
void packet_out::Encrypt()
{
if (!have_decrypted)
ABORT();
assert(have_decrypted);
if (have_encrypted)
return;
process_data();
#ifndef NONET
if (!DisableEncryption) {
auto lenCleartext = encrypted_buffer.size();
encrypted_buffer.insert(encrypted_buffer.begin(),
crypto_secretbox_NONCEBYTES, 0);
encrypted_buffer.insert(encrypted_buffer.end(),
crypto_secretbox_MACBYTES, 0);
randombytes_buf(encrypted_buffer.data(), crypto_secretbox_NONCEBYTES);
int status = crypto_secretbox_easy(
encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
lenCleartext,
encrypted_buffer.data(),
key.data());
if (status != 0)
ABORT();
}
#endif
auto lenCleartext = decrypted_buffer.size();
encrypted_buffer.insert(encrypted_buffer.begin(),
crypto_secretbox_NONCEBYTES, 0);
encrypted_buffer.insert(encrypted_buffer.end(),
crypto_secretbox_MACBYTES + lenCleartext, 0);
randombytes_buf(encrypted_buffer.data(), crypto_secretbox_NONCEBYTES);
int status = crypto_secretbox_easy(
encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
decrypted_buffer.data(),
lenCleartext,
encrypted_buffer.data(),
key.data());
if (status != 0)
ABORT();
have_encrypted = true;
}
#endif
packet_factory::packet_factory()
{
secure = false;
}
packet_factory::packet_factory(std::string pw)
{
#ifndef NONET
secure = false;
#ifdef PACKET_ENCRYPTION
if (sodium_init() < 0)
ABORT();
pw.resize(std::min<std::size_t>(pw.size(), crypto_pwhash_argon2id_PASSWD_MAX));
@ -235,6 +272,7 @@ packet_factory::packet_factory(std::string pw)
crypto_pwhash_ALG_ARGON2ID13);
if (status != 0)
ABORT();
secure = true;
#endif
}

31
Source/dvlnet/packet.h

@ -5,7 +5,7 @@
#include <memory>
#include <array>
#include <cstring>
#ifndef NONET
#ifdef PACKET_ENCRYPTION
#include <sodium.h>
#endif
@ -35,7 +35,7 @@ typedef uint8_t plr_t;
typedef uint32_t cookie_t;
typedef int turn_t; // change int to something else in devilution code later
typedef int leaveinfo_t; // also change later
#ifndef NONET
#ifdef PACKET_ENCRYPTION
typedef std::array<unsigned char, crypto_secretbox_KEYBYTES> key_t;
#else
// Stub out the key_t defintion as we're not doing any encryption.
@ -115,7 +115,7 @@ public:
void process_element(buffer_t &x);
template <class T>
void process_element(T &x);
void Decrypt();
void Decrypt(buffer_t buf);
};
class packet_out : public packet_proc<packet_out> {
@ -132,6 +132,7 @@ public:
static const unsigned char *begin(const T &x);
template <class T>
static const unsigned char *end(const T &x);
static cookie_t GenerateCookie();
void Encrypt();
};
@ -307,13 +308,13 @@ inline void packet_out::create<PT_DISCONNECT>(plr_t s, plr_t d, plr_t n,
inline void packet_out::process_element(buffer_t &x)
{
encrypted_buffer.insert(encrypted_buffer.end(), x.begin(), x.end());
decrypted_buffer.insert(decrypted_buffer.end(), x.begin(), x.end());
}
template <class T>
void packet_out::process_element(T &x)
{
encrypted_buffer.insert(encrypted_buffer.end(), begin(x), end(x));
decrypted_buffer.insert(decrypted_buffer.end(), begin(x), end(x));
}
template <class T>
@ -330,11 +331,13 @@ const unsigned char *packet_out::end(const T &x)
class packet_factory {
key_t key = {};
bool secure;
public:
static constexpr unsigned short max_packet_size = 0xFFFF;
packet_factory(std::string pw = "");
packet_factory();
packet_factory(std::string pw);
std::unique_ptr<packet> make_packet(buffer_t buf);
template <packet_type t, typename... Args>
std::unique_ptr<packet> make_packet(Args... args);
@ -343,8 +346,16 @@ public:
inline std::unique_ptr<packet> packet_factory::make_packet(buffer_t buf)
{
auto ret = std::make_unique<packet_in>(key);
#ifndef PACKET_ENCRYPTION
ret->Create(std::move(buf));
ret->Decrypt();
#else
if (!secure)
ret->Create(std::move(buf));
else
ret->Decrypt(std::move(buf));
#endif
size_t size = ret->Data().size();
ret->process_data();
return ret;
}
@ -353,7 +364,11 @@ std::unique_ptr<packet> packet_factory::make_packet(Args... args)
{
auto ret = std::make_unique<packet_out>(key);
ret->create<t>(args...);
ret->Encrypt();
ret->process_data();
#ifdef PACKET_ENCRYPTION
if (secure)
ret->Encrypt();
#endif
return ret;
}

13
Source/dvlnet/tcp_client.cpp

@ -6,7 +6,6 @@
#include <exception>
#include <functional>
#include <memory>
#include <sodium.h>
#include <sstream>
#include <stdexcept>
#include <system_error>
@ -16,24 +15,23 @@
namespace devilution {
namespace net {
int tcp_client::create(std::string addrstr, std::string passwd)
int tcp_client::create(std::string addrstr)
{
try {
auto port = sgOptions.Network.nPort;
local_server = std::make_unique<tcp_server>(ioc, addrstr, port, passwd);
return join(local_server->LocalhostSelf(), passwd);
local_server = std::make_unique<tcp_server>(ioc, addrstr, port, *pktfty);
return join(local_server->LocalhostSelf());
} catch (std::system_error &e) {
SDL_SetError("%s", e.what());
return -1;
}
}
int tcp_client::join(std::string addrstr, std::string passwd)
int tcp_client::join(std::string addrstr)
{
constexpr int MsSleep = 10;
constexpr int NoSleep = 250;
setup_password(passwd);
try {
std::stringstream port;
port << sgOptions.Network.nPort;
@ -46,8 +44,7 @@ int tcp_client::join(std::string addrstr, std::string passwd)
}
StartReceive();
{
randombytes_buf(reinterpret_cast<unsigned char *>(&cookie_self),
sizeof(cookie_t));
cookie_self = packet_out::GenerateCookie();
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(PLR_BROADCAST,
PLR_MASTER, cookie_self,
game_init_info);

4
Source/dvlnet/tcp_client.h

@ -17,8 +17,8 @@ namespace net {
class tcp_client : public base {
public:
int create(std::string addrstr, std::string passwd);
int join(std::string addrstr, std::string passwd);
int create(std::string addrstr);
int join(std::string addrstr);
virtual void poll();
virtual void send(packet &pkt);

4
Source/dvlnet/tcp_server.cpp

@ -12,9 +12,9 @@ namespace devilution {
namespace net {
tcp_server::tcp_server(asio::io_context &ioc, const std::string &bindaddr,
unsigned short port, std::string pw)
unsigned short port, packet_factory &pktfty)
: ioc(ioc)
, pktfty(std::move(pw))
, pktfty(pktfty)
{
auto addr = asio::ip::address::from_string(bindaddr);
auto ep = asio::ip::tcp::endpoint(addr, port);

4
Source/dvlnet/tcp_server.h

@ -26,7 +26,7 @@ public:
class tcp_server {
public:
tcp_server(asio::io_context &ioc, const std::string &bindaddr,
unsigned short port, std::string pw);
unsigned short port, packet_factory &pktfty);
std::string LocalhostSelf();
void Close();
virtual ~tcp_server();
@ -52,7 +52,7 @@ private:
typedef std::shared_ptr<client_connection> scc;
asio::io_context &ioc;
packet_factory pktfty;
packet_factory &pktfty;
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
std::array<scc, MAX_PLRS> connections;
buffer_t game_init_info;

2
Source/multi.cpp

@ -51,6 +51,7 @@ DWORD sgdwGameLoops;
bool gbIsMultiplayer;
bool sgbTimeout;
char szPlayerName[128];
bool PublicGame;
BYTE gbDeltaSender;
bool sgbNetInited;
uint32_t player_state[MAX_PLRS];
@ -752,6 +753,7 @@ bool NetInit(bool bSinglePlayer)
nthread_terminate_game("SNetGetGameInfo1");
if (!SNetGetGameInfo(GAMEINFO_PASSWORD, szPlayerDescript, 128))
nthread_terminate_game("SNetGetGameInfo2");
PublicGame = DvlNet_IsPublicGame();
return true;
}

1
Source/multi.h

@ -45,6 +45,7 @@ extern GameData sgGameInitInfo;
extern bool gbSelectProvider;
extern bool gbIsMultiplayer;
extern char szPlayerName[128];
extern bool PublicGame;
extern BYTE gbDeltaSender;
extern uint32_t player_state[MAX_PLRS];

2
Source/storm/storm.h

@ -293,5 +293,7 @@ void DvlNet_SendInfoRequest();
void DvlNet_ClearGamelist();
std::vector<std::string> DvlNet_GetGamelist();
void DvlNet_SetPassword(std::string pw);
void DvlNet_ClearPassword();
bool DvlNet_IsPublicGame();
} // namespace devilution

27
Source/storm/storm_net.cpp

@ -16,6 +16,7 @@ namespace devilution {
static std::unique_ptr<net::abstract_net> dvlnet_inst;
static char gpszGameName[128] = {};
static char gpszGamePassword[128] = {};
static bool GameIsPublic = {};
#ifndef NONET
static SdlMutex storm_net_mutex;
@ -165,8 +166,10 @@ bool SNetCreateGame(const char *pszGameName, const char *pszGamePassword, char *
strncpy(gpszGameName, pszGameName, sizeof(gpszGameName) - 1);
if (pszGamePassword != nullptr)
strncpy(gpszGamePassword, pszGamePassword, sizeof(gpszGamePassword) - 1);
*playerID = dvlnet_inst->create(pszGameName, pszGamePassword);
DvlNet_SetPassword(pszGamePassword);
else
DvlNet_ClearPassword();
*playerID = dvlnet_inst->create(pszGameName);
return *playerID != -1;
}
@ -178,8 +181,10 @@ bool SNetJoinGame(char *pszGameName, char *pszGamePassword, int *playerID)
if (pszGameName != nullptr)
strncpy(gpszGameName, pszGameName, sizeof(gpszGameName) - 1);
if (pszGamePassword != nullptr)
strncpy(gpszGamePassword, pszGamePassword, sizeof(gpszGamePassword) - 1);
*playerID = dvlnet_inst->join(pszGameName, pszGamePassword);
DvlNet_SetPassword(pszGamePassword);
else
DvlNet_ClearPassword();
*playerID = dvlnet_inst->join(pszGameName);
return *playerID != -1;
}
@ -230,7 +235,21 @@ std::vector<std::string> DvlNet_GetGamelist()
void DvlNet_SetPassword(std::string pw)
{
GameIsPublic = false;
strncpy(gpszGamePassword, pw.c_str(), sizeof(gpszGamePassword) - 1);
dvlnet_inst->setup_password(std::move(pw));
}
void DvlNet_ClearPassword()
{
GameIsPublic = true;
gpszGamePassword[0] = '\0';
dvlnet_inst->clear_password();
}
bool DvlNet_IsPublicGame()
{
return GameIsPublic;
}
} // namespace devilution

5
vcpkg.json

@ -4,10 +4,13 @@
"dependencies": [
"fmt",
"libpng",
"libsodium",
"sdl2"
],
"features": {
"encryption": {
"description": "Build libsodium for packet encryption",
"dependencies": [ "libsodium" ]
},
"translations": {
"description": "Build translation files",
"dependencies": [

Loading…
Cancel
Save