diff --git a/CMakeLists.txt b/CMakeLists.txt index d8470e422..cafcef97c 100644 --- a/CMakeLists.txt +++ b/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}) diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 2382187ae..fceec9fc6 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/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(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__ diff --git a/Source/DiabloUI/selgame.cpp b/Source/DiabloUI/selgame.cpp index 11d791029..549668802 100644 --- a/Source/DiabloUI/selgame.cpp +++ b/Source/DiabloUI/selgame.cpp @@ -41,8 +41,6 @@ std::vector> vecSelGameDialog; std::vector 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(_("Select Action"), rect4, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiSilver, 3)); +#ifdef PACKET_ENCRYPTION vecSelGameDlgItems.push_back(std::make_unique(_("Create Game"), 0)); - vecSelGameDlgItems.push_back(std::make_unique(_("Join Game"), 1)); +#endif + vecSelGameDlgItems.push_back(std::make_unique(_("Create Public Game"), 1)); + vecSelGameDlgItems.push_back(std::make_unique(_("Join Game"), 2)); for (unsigned i = 0; i < Gamelist.size(); i++) { - vecSelGameDlgItems.push_back(std::make_unique(Gamelist[i].c_str(), i + 2)); + vecSelGameDlgItems.push_back(std::make_unique(Gamelist[i].c_str(), i + 3)); } vecSelGameDialog.push_back(std::make_unique(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(_("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(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(_("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(_("Enter address"), selgame_Ip, 128, rect5, UiFlags::FontSize24 | UiFlags::ColorUiGold)); + vecSelGameDialog.push_back(std::make_unique(_("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(_("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(_("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(_("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(_("Enter Password"), selgame_Password, 15, rect5, UiFlags::FontSize24 | UiFlags::ColorUiGold)); + vecSelGameDialog.push_back(std::make_unique(_("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(_("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) { diff --git a/Source/DiabloUI/selhero.cpp b/Source/DiabloUI/selhero.cpp index 06458301b..830cf4c1f 100644 --- a/Source/DiabloUI/selhero.cpp +++ b/Source/DiabloUI/selhero.cpp @@ -275,7 +275,7 @@ void SelheroClassSelectorSelect(int value) vecSelDlgItems.push_back(std::make_unique(_("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(_("Enter Name"), selhero_heroInfo.name, 15, rect2, UiFlags::FontSize24 | UiFlags::ColorUiGold)); + vecSelDlgItems.push_back(std::make_unique(_("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(_("OK"), &UiFocusNavigationSelect, rect3, UiFlags::AlignCenter | UiFlags::FontSize30 | UiFlags::ColorUiGold)); diff --git a/Source/DiabloUI/ui_item.h b/Source/DiabloUI/ui_item.h index 9e9c9f1a7..d1984f4b7 100644 --- a/Source/DiabloUI/ui_item.h +++ b/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; }; //============================================================================= diff --git a/Source/automap.cpp b/Source/automap.cpp index 0b7a4bc03..f3c97bc85 100644 --- a/Source/automap.cpp +++ b/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) { diff --git a/Source/dvlnet/abstract_net.h b/Source/dvlnet/abstract_net.h index 0bca69406..996fd4c6e 100644 --- a/Source/dvlnet/abstract_net.h +++ b/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() { } diff --git a/Source/dvlnet/base.cpp b/Source/dvlnet/base.cpp index 06e234810..f75aed08a 100644 --- a/Source/dvlnet/base.cpp +++ b/Source/dvlnet/base.cpp @@ -17,6 +17,11 @@ void base::setup_password(std::string pw) pktfty = std::make_unique(pw); } +void base::clear_password() +{ + pktfty = std::make_unique(); +} + void base::RunEventHandler(_SNETEVENT &ev) { auto f = registered_handlers[static_cast(ev.eventid)]; diff --git a/Source/dvlnet/base.h b/Source/dvlnet/base.h index 8b82a0773..895a06c71 100644 --- a/Source/dvlnet/base.h +++ b/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; diff --git a/Source/dvlnet/base_protocol.h b/Source/dvlnet/base_protocol.h index 741e65c41..0259696c7 100644 --- a/Source/dvlnet/base_protocol.h +++ b/Source/dvlnet/base_protocol.h @@ -16,8 +16,8 @@ namespace net { template 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

::send_info_request() template void base_protocol

::wait_join() { - randombytes_buf(reinterpret_cast(&cookie_self), - sizeof(cookie_t)); + cookie_self = packet_out::GenerateCookie(); auto pkt = pktfty->make_packet(PLR_BROADCAST, PLR_MASTER, cookie_self, game_init_info); proto.send(firstpeer, pkt->Data()); @@ -122,9 +121,8 @@ void base_protocol

::wait_join() } template -int base_protocol

::create(std::string addrstr, std::string passwd) +int base_protocol

::create(std::string addrstr) { - setup_password(passwd); gamename = addrstr; if (wait_network()) { @@ -136,10 +134,8 @@ int base_protocol

::create(std::string addrstr, std::string passwd) } template -int base_protocol

::join(std::string addrstr, std::string passwd) +int base_protocol

::join(std::string addrstr) { - //addrstr = "fd80:56c2:e21c:0:199:931d:b14:c4d2"; - setup_password(passwd); gamename = addrstr; if (wait_network()) if (wait_firstpeer()) diff --git a/Source/dvlnet/cdwrap.h b/Source/dvlnet/cdwrap.h index 413c91134..5919dbdd0 100644 --- a/Source/dvlnet/cdwrap.h +++ b/Source/dvlnet/cdwrap.h @@ -17,12 +17,13 @@ private: std::unique_ptr dvlnet_wrap; std::map registered_handlers; buffer_t game_init_info; + std::optional 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 get_gamelist(); virtual void setup_password(std::string pw); + virtual void clear_password(); cdwrap(); virtual ~cdwrap() = default; @@ -58,23 +60,28 @@ void cdwrap::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 -int cdwrap::create(std::string addrstr, std::string passwd) +int cdwrap::create(std::string addrstr) { reset(); - return dvlnet_wrap->create(addrstr, passwd); + return dvlnet_wrap->create(addrstr); } template -int cdwrap::join(std::string addrstr, std::string passwd) +int cdwrap::join(std::string addrstr) { game_init_info = buffer_t(); reset(); - return dvlnet_wrap->join(addrstr, passwd); + return dvlnet_wrap->join(addrstr); } template @@ -186,8 +193,16 @@ std::vector cdwrap::get_gamelist() template void cdwrap::setup_password(std::string pw) { + game_pw = pw; return dvlnet_wrap->setup_password(pw); } +template +void cdwrap::clear_password() +{ + game_pw = std::nullopt; + return dvlnet_wrap->clear_password(); +} + } // namespace net } // namespace devilution diff --git a/Source/dvlnet/loopback.cpp b/Source/dvlnet/loopback.cpp index 7adfe0f2c..5549bea52 100644 --- a/Source/dvlnet/loopback.cpp +++ b/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(); } diff --git a/Source/dvlnet/loopback.h b/Source/dvlnet/loopback.h index 7999954e1..048081228 100644 --- a/Source/dvlnet/loopback.h +++ b/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); diff --git a/Source/dvlnet/packet.cpp b/Source/dvlnet/packet.cpp index 5cd894d33..d5d566001 100644 --- a/Source/dvlnet/packet.cpp +++ b/Source/dvlnet/packet.cpp @@ -1,10 +1,52 @@ +#ifdef PACKET_ENCRYPTION +#include +#else +#include +#include +#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(&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 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 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(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 } diff --git a/Source/dvlnet/packet.h b/Source/dvlnet/packet.h index 07adc3578..617202d6b 100644 --- a/Source/dvlnet/packet.h +++ b/Source/dvlnet/packet.h @@ -5,7 +5,7 @@ #include #include #include -#ifndef NONET +#ifdef PACKET_ENCRYPTION #include #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 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 void process_element(T &x); - void Decrypt(); + void Decrypt(buffer_t buf); }; class packet_out : public packet_proc { @@ -132,6 +132,7 @@ public: static const unsigned char *begin(const T &x); template static const unsigned char *end(const T &x); + static cookie_t GenerateCookie(); void Encrypt(); }; @@ -307,13 +308,13 @@ inline void packet_out::create(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 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 @@ -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 make_packet(buffer_t buf); template std::unique_ptr make_packet(Args... args); @@ -343,8 +346,16 @@ public: inline std::unique_ptr packet_factory::make_packet(buffer_t buf) { auto ret = std::make_unique(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_factory::make_packet(Args... args) { auto ret = std::make_unique(key); ret->create(args...); - ret->Encrypt(); + ret->process_data(); +#ifdef PACKET_ENCRYPTION + if (secure) + ret->Encrypt(); +#endif return ret; } diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp index f1396d6bc..ae931ebaf 100644 --- a/Source/dvlnet/tcp_client.cpp +++ b/Source/dvlnet/tcp_client.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -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(ioc, addrstr, port, passwd); - return join(local_server->LocalhostSelf(), passwd); + local_server = std::make_unique(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(&cookie_self), - sizeof(cookie_t)); + cookie_self = packet_out::GenerateCookie(); auto pkt = pktfty->make_packet(PLR_BROADCAST, PLR_MASTER, cookie_self, game_init_info); diff --git a/Source/dvlnet/tcp_client.h b/Source/dvlnet/tcp_client.h index 6fa2c4976..35b9df0db 100644 --- a/Source/dvlnet/tcp_client.h +++ b/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); diff --git a/Source/dvlnet/tcp_server.cpp b/Source/dvlnet/tcp_server.cpp index 1bc840f6e..b520d85e8 100644 --- a/Source/dvlnet/tcp_server.cpp +++ b/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); diff --git a/Source/dvlnet/tcp_server.h b/Source/dvlnet/tcp_server.h index 6a718fa5d..d9ce7c9f2 100644 --- a/Source/dvlnet/tcp_server.h +++ b/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 scc; asio::io_context &ioc; - packet_factory pktfty; + packet_factory &pktfty; std::unique_ptr acceptor; std::array connections; buffer_t game_init_info; diff --git a/Source/multi.cpp b/Source/multi.cpp index 735f9b6ec..ac0907724 100644 --- a/Source/multi.cpp +++ b/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; } diff --git a/Source/multi.h b/Source/multi.h index 9836aae0a..c556e8ca6 100644 --- a/Source/multi.h +++ b/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]; diff --git a/Source/storm/storm.h b/Source/storm/storm.h index b8b0fca6a..bb806c2bc 100644 --- a/Source/storm/storm.h +++ b/Source/storm/storm.h @@ -293,5 +293,7 @@ void DvlNet_SendInfoRequest(); void DvlNet_ClearGamelist(); std::vector DvlNet_GetGamelist(); void DvlNet_SetPassword(std::string pw); +void DvlNet_ClearPassword(); +bool DvlNet_IsPublicGame(); } // namespace devilution diff --git a/Source/storm/storm_net.cpp b/Source/storm/storm_net.cpp index 6a906cfcc..95228f3c5 100644 --- a/Source/storm/storm_net.cpp +++ b/Source/storm/storm_net.cpp @@ -16,6 +16,7 @@ namespace devilution { static std::unique_ptr 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 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 diff --git a/vcpkg.json b/vcpkg.json index 2422129e1..0d2243d09 100644 --- a/vcpkg.json +++ b/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": [