Browse Source

Add multiplayer event logging

pull/8286/head
LP 4 months ago committed by GitHub
parent
commit
f295e67b9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CMake/Tests.cmake
  2. 3
      Source/appfat.cpp
  3. 4
      Source/dvlnet/abstract_net.h
  4. 8
      Source/dvlnet/base.cpp
  5. 4
      Source/dvlnet/base.h
  6. 4
      Source/dvlnet/base_protocol.h
  7. 4
      Source/dvlnet/cdwrap.cpp
  8. 4
      Source/dvlnet/cdwrap.h
  9. 15
      Source/dvlnet/leaveinfo.hpp
  10. 4
      Source/dvlnet/loopback.cpp
  11. 4
      Source/dvlnet/loopback.h
  12. 14
      Source/dvlnet/packet.h
  13. 2
      Source/dvlnet/tcp_client.cpp
  14. 2
      Source/dvlnet/tcp_client.h
  15. 2
      Source/dvlnet/tcp_server.cpp
  16. 9
      Source/dvlnet/zerotier_native.cpp
  17. 4
      Source/main.cpp
  18. 3
      Source/monster.cpp
  19. 20
      Source/msg.cpp
  20. 5
      Source/msg.h
  21. 75
      Source/multi.cpp
  22. 8
      Source/multi.h
  23. 2
      Source/platform/ctr/system.cpp
  24. 42
      Source/storm/storm_net.cpp
  25. 10
      Source/storm/storm_net.hpp
  26. 28
      test/multi_logging_test.cpp

1
CMake/Tests.cmake

@ -27,6 +27,7 @@ set(tests
items_test
math_test
missiles_test
multi_logging_test
pack_test
player_test
quests_test

3
Source/appfat.cpp

@ -21,6 +21,7 @@
#include <fmt/format.h>
#include "diablo.h"
#include "dvlnet/leaveinfo.hpp"
#include "multi.h"
#include "storm/storm_net.hpp"
#include "utils/language.h"
@ -50,7 +51,7 @@ void FreeDlg()
CleanupThreadId = this_sdl_thread::get_id();
if (gbIsMultiplayer) {
if (SNetLeaveGame(3))
if (SNetLeaveGame(leaveinfo_t::LEAVE_EXIT))
SDL_Delay(2000);
}

4
Source/dvlnet/abstract_net.h

@ -25,8 +25,8 @@ public:
virtual void SNetGetProviderCaps(struct _SNETCAPS *caps) = 0;
virtual bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) = 0;
virtual bool SNetUnregisterEventHandler(event_type evtype) = 0;
virtual bool SNetLeaveGame(int type) = 0;
virtual bool SNetDropPlayer(int playerid, uint32_t flags) = 0;
virtual bool SNetLeaveGame(net::leaveinfo_t type) = 0;
virtual bool SNetDropPlayer(int playerid, net::leaveinfo_t flags) = 0;
virtual bool SNetGetOwnerTurnsWaiting(uint32_t *turns) = 0;
virtual bool SNetGetTurnsInTransit(uint32_t *turns) = 0;
virtual void setup_gameinfo(buffer_t info) = 0;

8
Source/dvlnet/base.cpp

@ -469,11 +469,11 @@ bool base::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func)
return true;
}
bool base::SNetLeaveGame(int type)
bool base::SNetLeaveGame(net::leaveinfo_t type)
{
tl::expected<std::unique_ptr<packet>, PacketError> pkt
= pktfty->make_packet<PT_DISCONNECT>(
plr_self, PLR_BROADCAST, plr_self, static_cast<leaveinfo_t>(type));
plr_self, PLR_BROADCAST, plr_self, type);
if (!pkt.has_value()) {
LogError("make_packet: {}", pkt.error().what());
return false;
@ -487,7 +487,7 @@ bool base::SNetLeaveGame(int type)
return true;
}
bool base::SNetDropPlayer(int playerid, uint32_t flags)
bool base::SNetDropPlayer(int playerid, net::leaveinfo_t flags)
{
const plr_t plr = static_cast<plr_t>(playerid);
tl::expected<std::unique_ptr<packet>, PacketError> pkt
@ -495,7 +495,7 @@ bool base::SNetDropPlayer(int playerid, uint32_t flags)
plr_self,
PLR_BROADCAST,
plr,
static_cast<leaveinfo_t>(flags));
flags);
if (!pkt.has_value()) {
LogError("make_packet: {}", pkt.error().what());
return false;

4
Source/dvlnet/base.h

@ -25,8 +25,8 @@ public:
void SNetGetProviderCaps(struct _SNETCAPS *caps) override;
bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) override;
bool SNetUnregisterEventHandler(event_type evtype) override;
bool SNetLeaveGame(int type) override;
bool SNetDropPlayer(int playerid, uint32_t flags) override;
bool SNetLeaveGame(net::leaveinfo_t type) override;
bool SNetDropPlayer(int playerid, net::leaveinfo_t flags) override;
bool SNetGetOwnerTurnsWaiting(uint32_t *turns) override;
bool SNetGetTurnsInTransit(uint32_t *turns) override;

4
Source/dvlnet/base_protocol.h

@ -31,7 +31,7 @@ public:
tl::expected<void, PacketError> send(packet &pkt) override;
void DisconnectNet(plr_t plr) override;
bool SNetLeaveGame(int type) override;
bool SNetLeaveGame(net::leaveinfo_t type) override;
std::string make_default_gamename() override;
bool send_info_request() override;
@ -576,7 +576,7 @@ DvlNetLatencies base_protocol<P>::get_latencies(uint8_t playerid)
}
template <class P>
bool base_protocol<P>::SNetLeaveGame(int type)
bool base_protocol<P>::SNetLeaveGame(net::leaveinfo_t type)
{
auto ret = base::SNetLeaveGame(type);
recv();

4
Source/dvlnet/cdwrap.cpp

@ -78,12 +78,12 @@ bool cdwrap::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func)
return true;
}
bool cdwrap::SNetLeaveGame(int type)
bool cdwrap::SNetLeaveGame(net::leaveinfo_t type)
{
return dvlnet_wrap->SNetLeaveGame(type);
}
bool cdwrap::SNetDropPlayer(int playerid, uint32_t flags)
bool cdwrap::SNetDropPlayer(int playerid, net::leaveinfo_t flags)
{
return dvlnet_wrap->SNetDropPlayer(playerid, flags);
}

4
Source/dvlnet/cdwrap.h

@ -41,8 +41,8 @@ public:
void SNetGetProviderCaps(struct _SNETCAPS *caps) override;
bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) override;
bool SNetUnregisterEventHandler(event_type evtype) override;
bool SNetLeaveGame(int type) override;
bool SNetDropPlayer(int playerid, uint32_t flags) override;
bool SNetLeaveGame(net::leaveinfo_t type) override;
bool SNetDropPlayer(int playerid, net::leaveinfo_t flags) override;
bool SNetGetOwnerTurnsWaiting(uint32_t *turns) override;
bool SNetGetTurnsInTransit(uint32_t *turns) override;
void setup_gameinfo(buffer_t info) override;

15
Source/dvlnet/leaveinfo.hpp

@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
namespace devilution {
namespace net {
enum class leaveinfo_t : uint32_t {
LEAVE_EXIT = 3,
LEAVE_ENDING = 0x40000004,
LEAVE_DROP = 0x40000006,
};
} // namespace net
} // namespace devilution

4
Source/dvlnet/loopback.cpp

@ -85,13 +85,13 @@ bool loopback::SNetUnregisterEventHandler(event_type /*evtype*/)
return true;
}
bool loopback::SNetLeaveGame(int /*type*/)
bool loopback::SNetLeaveGame(net::leaveinfo_t /*type*/)
{
IsLoopback = false;
return true;
}
bool loopback::SNetDropPlayer(int /*playerid*/, uint32_t /*flags*/)
bool loopback::SNetDropPlayer(int /*playerid*/, net::leaveinfo_t /*flags*/)
{
return true;
}

4
Source/dvlnet/loopback.h

@ -26,8 +26,8 @@ public:
void SNetGetProviderCaps(struct _SNETCAPS *caps) override;
bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func) override;
bool SNetUnregisterEventHandler(event_type evtype) override;
bool SNetLeaveGame(int type) override;
bool SNetDropPlayer(int playerid, uint32_t flags) override;
bool SNetLeaveGame(net::leaveinfo_t type) override;
bool SNetDropPlayer(int playerid, net::leaveinfo_t flags) override;
bool SNetGetOwnerTurnsWaiting(uint32_t *turns) override;
bool SNetGetTurnsInTransit(uint32_t *turns) override;
void setup_gameinfo(buffer_t info) override;

14
Source/dvlnet/packet.h

@ -15,6 +15,7 @@
#include "appfat.h"
#include "dvlnet/abstract_net.h"
#include "dvlnet/leaveinfo.hpp"
#include "utils/attributes.h"
#include "utils/endian_read.hpp"
#include "utils/endian_write.hpp"
@ -46,7 +47,6 @@ typedef uint8_t plr_t;
typedef uint8_t seq_t;
typedef uint32_t cookie_t;
typedef uint32_t timestamp_t;
typedef uint32_t leaveinfo_t;
#ifdef PACKET_ENCRYPTION
typedef std::array<unsigned char, crypto_secretbox_KEYBYTES> key_t;
#else
@ -404,11 +404,19 @@ tl::expected<void, PacketError> packet_out::process_element(const T &x)
static_assert(sizeof(T) == 4 || sizeof(T) == 2 || sizeof(T) == 1, "Unsupported T");
if (sizeof(T) == 4) {
unsigned char buf[4];
WriteLE32(buf, x);
if constexpr (std::is_enum<T>::value) {
WriteLE32(buf, static_cast<std::underlying_type_t<T>>(x));
} else {
WriteLE32(buf, x);
}
decrypted_buffer.insert(decrypted_buffer.end(), buf, buf + 4);
} else if (sizeof(T) == 2) {
unsigned char buf[2];
WriteLE16(buf, x);
if constexpr (std::is_enum<T>::value) {
WriteLE16(buf, static_cast<std::underlying_type_t<T>>(x));
} else {
WriteLE16(buf, x);
}
decrypted_buffer.insert(decrypted_buffer.end(), buf, buf + 2);
} else if (sizeof(T) == 1) {
decrypted_buffer.push_back(static_cast<unsigned char>(x));

2
Source/dvlnet/tcp_client.cpp

@ -212,7 +212,7 @@ void tcp_client::DisconnectNet(plr_t plr)
local_server->DisconnectNet(plr);
}
bool tcp_client::SNetLeaveGame(int type)
bool tcp_client::SNetLeaveGame(net::leaveinfo_t type)
{
auto ret = base::SNetLeaveGame(type);
poll();

2
Source/dvlnet/tcp_client.h

@ -34,7 +34,7 @@ public:
tl::expected<void, PacketError> send(packet &pkt) override;
void DisconnectNet(plr_t plr) override;
bool SNetLeaveGame(int type) override;
bool SNetLeaveGame(net::leaveinfo_t type) override;
~tcp_client() override;

2
Source/dvlnet/tcp_server.cpp

@ -267,7 +267,7 @@ void tcp_server::DropConnection(const scc &con)
tl::expected<std::unique_ptr<packet>, PacketError> pkt
= pktfty.make_packet<PT_DISCONNECT>(PLR_MASTER, PLR_BROADCAST,
plr, static_cast<leaveinfo_t>(LEAVE_DROP));
plr, leaveinfo_t::LEAVE_DROP);
if (pkt.has_value()) {
SendPacket(**pkt);
} else {

9
Source/dvlnet/zerotier_native.cpp

@ -25,7 +25,9 @@
#ifdef DVL_ZT_SYMLINK
#include <shlobj.h>
#ifdef PACKET_ENCRYPTION
#include <sodium.h>
#endif
#include "utils/str_cat.hpp"
#include "utils/utf8.hpp"
@ -61,6 +63,7 @@ bool HasMultiByteChars(std::string_view path)
return c_any_of(path, IsTrailUtf8CodeUnit);
}
#ifdef PACKET_ENCRYPTION
std::string ComputeAlternateFolderName(std::string_view path)
{
const size_t hashSize = crypto_generichash_BYTES;
@ -79,6 +82,12 @@ std::string ComputeAlternateFolderName(std::string_view path)
}
return std::string(buf, hashSize * 2);
}
#else
std::string ComputeAlternateFolderName(std::string_view path)
{
return {};
}
#endif
std::string ToZTCompliantPath(std::string_view configPath)
{

4
Source/main.cpp

@ -38,15 +38,19 @@ extern "C" int main(int argc, char **argv)
#ifdef __SWITCH__
switch_romfs_init();
switch_enable_network();
#ifdef PACKET_ENCRYPTION
randombytes_switchrandom_init();
#endif
#endif
#ifdef __3DS__
ctr_sys_init();
#endif
#ifdef __vita__
vita_enable_network();
#ifdef PACKET_ENCRYPTION
randombytes_vitarandom_init();
#endif
#endif
#ifdef NXDK
nxMountDrive('E', "\\Device\\Harddisk0\\Partition1\\");
#endif

3
Source/monster.cpp

@ -39,6 +39,7 @@
#include "cursor.h"
#include "dead.h"
#include "diablo.h"
#include "dvlnet/leaveinfo.hpp"
#include "effects.h"
#include "engine/animationinfo.h"
#include "engine/clx_sprite.hpp"
@ -3947,7 +3948,7 @@ void M_UpdateRelations(const Monster &monster)
void DoEnding()
{
if (gbIsMultiplayer) {
SNetLeaveGame(LEAVE_ENDING);
SNetLeaveGame(leaveinfo_t::LEAVE_ENDING);
}
music_stop();

20
Source/msg.cpp

@ -444,7 +444,7 @@ void PrePacket()
auto *cmd = reinterpret_cast<TFakeDropPlr *>(data);
data += sizeof(*cmd);
remainingBytes -= sizeof(*cmd);
multi_player_left(cmd->bPlr, Swap32LE(cmd->dwReason));
multi_player_left(cmd->bPlr, static_cast<leaveinfo_t>(Swap32LE(cmd->dwReason)));
continue;
}
@ -780,7 +780,7 @@ void DeltaImportData(_cmd_id cmd, uint32_t recvOffset, int pnum)
deltaSize = PkwareDecompress(&sgRecvBuf[1], static_cast<uint32_t>(deltaSize), sizeof(sgRecvBuf) - 1);
if (deltaSize == 0) {
Log("PKWare decompression failure, dropping player {}", pnum);
SNetDropPlayer(pnum, LEAVE_DROP);
SNetDropPlayer(pnum, leaveinfo_t::LEAVE_DROP);
return;
}
}
@ -800,13 +800,13 @@ void DeltaImportData(_cmd_id cmd, uint32_t recvOffset, int pnum)
src = DeltaImportSpawnedMonsters(src, end, deltaLevel.spawnedMonsters);
} else {
Log("Received invalid deltas, dropping player {}", pnum);
SNetDropPlayer(pnum, LEAVE_DROP);
SNetDropPlayer(pnum, leaveinfo_t::LEAVE_DROP);
return;
}
if (src == nullptr) {
Log("Received invalid deltas, dropping player {}", pnum);
SNetDropPlayer(pnum, LEAVE_DROP);
SNetDropPlayer(pnum, leaveinfo_t::LEAVE_DROP);
return;
}
@ -995,7 +995,7 @@ size_t OnLevelData(const TCmdPlrInfoHdr &message, size_t maxCmdSize, const Playe
if (sgdwRecvOffset + wBytes > sizeof(sgRecvBuf)) {
Log("Received too many deltas, dropping player {}", player.getId());
SNetDropPlayer(player.getId(), LEAVE_DROP);
SNetDropPlayer(player.getId(), leaveinfo_t::LEAVE_DROP);
return wBytes + sizeof(message);
}
@ -1115,7 +1115,7 @@ void DeltaPutItem(const TCmdPItem &message, Point position, const Player &player
&& item.def.dwSeed == message.def.dwSeed) {
if (item.bCmd != TCmdPItem::DroppedItem) {
Log("Suspicious floor item duplication, dropping player {}", player.getId());
SNetDropPlayer(player.getId(), LEAVE_DROP);
SNetDropPlayer(player.getId(), leaveinfo_t::LEAVE_DROP);
}
return;
}
@ -2694,11 +2694,11 @@ void ClearLastSentPlayerCmd()
lastSentPlayerCmd = {};
}
void msg_send_drop_pkt(uint8_t pnum, int reason)
void msg_send_drop_pkt(uint8_t pnum, leaveinfo_t reason)
{
TFakeDropPlr cmd;
cmd.dwReason = Swap32LE(reason);
cmd.dwReason = Swap32LE(static_cast<uint32_t>(reason));
cmd.bCmd = FAKE_CMD_DROPID;
cmd.bPlr = pnum;
BufferMessage(pnum, &cmd, sizeof(cmd));
@ -3310,7 +3310,7 @@ bool ValidateCmdSize(size_t requiredCmdSize, size_t maxCmdSize, size_t playerId)
return true;
Log("Suspiciously small packet size, dropping player {}", playerId);
SNetDropPlayer(static_cast<uint8_t>(playerId), LEAVE_DROP);
SNetDropPlayer(static_cast<uint8_t>(playerId), leaveinfo_t::LEAVE_DROP);
return false;
}
@ -3480,7 +3480,7 @@ size_t ParseCmd(uint8_t pnum, const TCmd *pCmd, size_t maxCmdSize)
if (pCmd->bCmd < CMD_DLEVEL || pCmd->bCmd > CMD_DLEVEL_END) {
Log("Unrecognized network message {}, dropping player {}", static_cast<uint8_t>(pCmd->bCmd), pnum);
SNetDropPlayer(pnum, LEAVE_DROP);
SNetDropPlayer(pnum, leaveinfo_t::LEAVE_DROP);
return 0;
}

5
Source/msg.h

@ -7,6 +7,7 @@
#include <cstdint>
#include "dvlnet/leaveinfo.hpp"
#include "engine/point.hpp"
#include "items.h"
#include "monster.h"
@ -16,6 +17,8 @@
namespace devilution {
using net::leaveinfo_t;
#define MAX_SEND_STR_LEN 80
enum _cmd_id : uint8_t {
@ -716,7 +719,7 @@ extern int dwRecCount;
void PrepareItemForNetwork(const Item &item, TItem &messageItem);
void PrepareEarForNetwork(const Item &item, TEar &ear);
void RecreateItem(const Player &player, const TItem &messageItem, Item &item);
void msg_send_drop_pkt(uint8_t pnum, int reason);
void msg_send_drop_pkt(uint8_t pnum, leaveinfo_t reason);
bool msg_wait_resync();
void run_delta_info();
void DeltaExportData(uint8_t pnum);

75
Source/multi.cpp

@ -44,6 +44,7 @@
#include "utils/endian_swap.hpp"
#include "utils/is_of.hpp"
#include "utils/language.h"
#include "utils/log.hpp"
#include "utils/str_cat.hpp"
namespace devilution {
@ -59,7 +60,7 @@ bool sgbSendDeltaTbl[MAX_PLRS];
GameData sgGameInitInfo;
bool gbSelectProvider;
int sglTimeoutStart;
uint32_t sgdwPlayerLeftReasonTbl[MAX_PLRS];
leaveinfo_t sgdwPlayerLeftReasonTbl[MAX_PLRS];
uint32_t sgdwGameLoops;
/**
* Specifies the maximum number of players in a game, where 1
@ -216,7 +217,7 @@ void CheckPlayerInfoTimeouts()
// Time the player out after 15 seconds
// if we do not receive valid player info
if (SDL_GetTicks() - timerStart >= 15000) {
SNetDropPlayer(i, LEAVE_DROP);
SNetDropPlayer(i, leaveinfo_t::LEAVE_DROP);
timerStart = 0;
}
}
@ -288,16 +289,27 @@ void PlayerLeftMsg(Player &player, bool left)
RemoveEnemyReferences(player);
RemovePlrMissiles(player);
if (left) {
const leaveinfo_t leaveReason = sgdwPlayerLeftReasonTbl[player.getId()];
const std::string reasonDescription = DescribeLeaveReason(leaveReason);
std::string_view pszFmt = _("Player '{:s}' just left the game");
switch (sgdwPlayerLeftReasonTbl[player.getId()]) {
case LEAVE_ENDING:
switch (leaveReason) {
case leaveinfo_t::LEAVE_EXIT:
break;
case leaveinfo_t::LEAVE_ENDING:
pszFmt = _("Player '{:s}' killed Diablo and left the game!");
gbSomebodyWonGameKludge = true;
break;
case LEAVE_DROP:
case leaveinfo_t::LEAVE_DROP:
pszFmt = _("Player '{:s}' dropped due to timeout");
break;
}
if (!IsLoopback) {
const uint8_t remainingPlayers = gbActivePlayers > 0 ? gbActivePlayers - 1 : 0;
if (player._pName[0] != '\0')
LogInfo("Player '{}' left the {} game ({}, {}/{} players)", player._pName, ConnectionNames[provider], reasonDescription, remainingPlayers, MAX_PLRS);
else
LogInfo("Player left the {} game ({}, {}/{} players)", ConnectionNames[provider], reasonDescription, remainingPlayers, MAX_PLRS);
}
EventPlrMsg(fmt::format(fmt::runtime(pszFmt), player._pName));
}
player.plractive = false;
@ -316,7 +328,7 @@ void ClearPlayerLeftState()
PlayerLeftMsg(Players[i], true);
sgbPlayerLeftGameTbl[i] = false;
sgdwPlayerLeftReasonTbl[i] = 0;
sgdwPlayerLeftReasonTbl[i] = static_cast<leaveinfo_t>(0);
}
}
}
@ -325,7 +337,7 @@ void CheckDropPlayer()
{
for (uint8_t i = 0; i < Players.size(); i++) {
if ((player_state[i] & PS_ACTIVE) == 0 && (player_state[i] & PS_CONNECTED) != 0) {
SNetDropPlayer(i, LEAVE_DROP);
SNetDropPlayer(i, leaveinfo_t::LEAVE_DROP);
}
}
}
@ -422,13 +434,14 @@ void HandleEvents(_SNETEVENT *pEvt)
sgbPlayerLeftGameTbl[playerId] = true;
sgbPlayerTurnBitTbl[playerId] = false;
int32_t leftReason = 0;
if (pEvt->data != nullptr && pEvt->databytes >= sizeof(leftReason)) {
std::memcpy(&leftReason, pEvt->data, sizeof(leftReason));
leftReason = Swap32LE(leftReason);
uint32_t leftReasonRaw = 0;
if (pEvt->data != nullptr && pEvt->databytes >= sizeof(leftReasonRaw)) {
std::memcpy(&leftReasonRaw, pEvt->data, sizeof(leftReasonRaw));
leftReasonRaw = Swap32LE(leftReasonRaw);
}
leaveinfo_t leftReason = static_cast<leaveinfo_t>(leftReasonRaw);
sgdwPlayerLeftReasonTbl[playerId] = leftReason;
if (leftReason == LEAVE_ENDING)
if (leftReason == leaveinfo_t::LEAVE_ENDING)
gbSomebodyWonGameKludge = true;
sgbSendDeltaTbl[playerId] = false;
@ -520,6 +533,26 @@ bool InitMulti(GameData *gameData)
} // namespace
DVL_API_FOR_TEST std::string DescribeLeaveReason(leaveinfo_t leaveReason)
{
switch (leaveReason) {
case leaveinfo_t::LEAVE_EXIT:
return "normal exit";
case leaveinfo_t::LEAVE_ENDING:
return "Diablo defeated";
case leaveinfo_t::LEAVE_DROP:
return "connection timeout";
default:
return fmt::format("code 0x{:08X}", static_cast<uint32_t>(leaveReason));
}
}
std::string FormatGameSeed(const uint32_t gameSeed[4])
{
return fmt::format("{:08X}{:08X}{:08X}{:08X}",
gameSeed[0], gameSeed[1], gameSeed[2], gameSeed[3]);
}
void InitGameInfo()
{
const xoshiro128plusplus gameGenerator = ReserveSeedSequence();
@ -597,7 +630,7 @@ void multi_msg_countdown()
}
}
void multi_player_left(uint8_t pnum, int reason)
void multi_player_left(uint8_t pnum, leaveinfo_t reason)
{
sgbPlayerLeftGameTbl[pnum] = true;
sgdwPlayerLeftReasonTbl[pnum] = reason;
@ -769,7 +802,7 @@ void NetClose()
nthread_cleanup();
tmsg_cleanup();
UnregisterNetEventHandlers();
SNetLeaveGame(3);
SNetLeaveGame(leaveinfo_t::LEAVE_EXIT);
if (gbIsMultiplayer)
SDL_Delay(2000);
if (!demo::IsRunning()) {
@ -843,6 +876,16 @@ bool NetInit(bool bSinglePlayer)
AddMessageToChatLog(_("New Game"), nullptr, UiFlags::ColorRed);
AddMessageToChatLog(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), myPlayer._pName, myPlayer.getCharacterLevel()));
// Log join message with seed for joining players (creator already logged it in SNetCreateGame)
if (gbIsMultiplayer && !IsLoopback && MyPlayerId != 0) {
std::string upperGameName = GameName;
std::transform(upperGameName.begin(), upperGameName.end(), upperGameName.begin(), ::toupper);
const char *privacy = PublicGame ? "public" : "private";
LogInfo("Joined {} {} multiplayer game '{}' (player id: {}, seed: {})",
privacy, ConnectionNames[provider], upperGameName, MyPlayerId,
FormatGameSeed(sgGameInitInfo.gameSeed));
}
return true;
}
@ -877,7 +920,7 @@ void recv_plrinfo(Player &player, const TCmdPlrInfoHdr &header, bool recv)
PlayerLeftMsg(player, false);
if (!UnPackNetPlayer(packedPlayer, player)) {
player = {};
SNetDropPlayer(pnum, LEAVE_DROP);
SNetDropPlayer(pnum, leaveinfo_t::LEAVE_DROP);
return;
}
@ -892,6 +935,8 @@ void recv_plrinfo(Player &player, const TCmdPlrInfoHdr &header, bool recv)
std::string_view szEvent;
if (sgbPlayerTurnBitTbl[pnum]) {
szEvent = _("Player '{:s}' (level {:d}) just joined the game");
if (!IsLoopback)
LogInfo("Player '{}' joined the {} game (level {}, {}/{} players)", player._pName, ConnectionNames[provider], player.getCharacterLevel(), gbActivePlayers, MAX_PLRS);
} else {
szEvent = _("Player '{:s}' (level {:d}) is already in the game");
}

8
Source/multi.h

@ -9,11 +9,14 @@
#include <string>
#include <vector>
#include "dvlnet/leaveinfo.hpp"
#include "msg.h"
#include "utils/attributes.h"
namespace devilution {
using net::leaveinfo_t;
// Defined in player.h, forward declared here to allow for functions which operate in the context of a player.
struct Player;
@ -63,12 +66,15 @@ extern uint8_t gbDeltaSender;
extern uint32_t player_state[MAX_PLRS];
extern bool IsLoopback;
DVL_API_FOR_TEST std::string DescribeLeaveReason(leaveinfo_t leaveReason);
std::string FormatGameSeed(const uint32_t gameSeed[4]);
void InitGameInfo();
void NetSendLoPri(uint8_t playerId, const std::byte *data, size_t size);
void NetSendHiPri(uint8_t playerId, const std::byte *data, size_t size);
void multi_send_msg_packet(uint32_t pmask, const std::byte *data, size_t size);
void multi_msg_countdown();
void multi_player_left(uint8_t pnum, int reason);
void multi_player_left(uint8_t pnum, leaveinfo_t reason);
void multi_net_ping();
/**

2
Source/platform/ctr/system.cpp

@ -114,9 +114,11 @@ void ctr_sys_init()
n3ds_socInit();
atexit([]() { n3ds_socExit(); });
#ifdef PACKET_ENCRYPTION
randombytes_ctrrandom_init();
atexit([]() {
if (psGetSessionHandle())
psExit();
});
#endif
}

42
Source/storm/storm_net.cpp

@ -1,5 +1,6 @@
#include "storm/storm_net.hpp"
#include <algorithm>
#include <cstdint>
#include <memory>
@ -17,6 +18,7 @@
#include "menu.h"
#include "multi.h"
#include "options.h"
#include "utils/log.hpp"
#include "utils/stubs.h"
#include "utils/utf8.hpp"
@ -100,7 +102,7 @@ bool SNetDestroy()
return true;
}
bool SNetDropPlayer(uint8_t playerid, uint32_t flags)
bool SNetDropPlayer(uint8_t playerid, leaveinfo_t flags)
{
#ifndef NONET
std::lock_guard<SdlMutex> lg(storm_net_mutex);
@ -108,13 +110,20 @@ bool SNetDropPlayer(uint8_t playerid, uint32_t flags)
return dvlnet_inst->SNetDropPlayer(playerid, flags);
}
bool SNetLeaveGame(int type)
bool SNetLeaveGame(leaveinfo_t type)
{
#ifndef NONET
std::lock_guard<SdlMutex> lg(storm_net_mutex);
#endif
if (dvlnet_inst == nullptr)
return true;
if (!IsLoopback) {
std::string upperGameName = GameName;
std::transform(upperGameName.begin(), upperGameName.end(), upperGameName.begin(), ::toupper);
const std::string reasonDescription = DescribeLeaveReason(type);
LogInfo("Leaving {} multiplayer game '{}' (reason: {})",
ConnectionNames[provider], upperGameName, reasonDescription);
}
return dvlnet_inst->SNetLeaveGame(type);
}
@ -156,8 +165,25 @@ bool SNetCreateGame(const char *pszGameName, const char *pszGamePassword, char *
DvlNet_SetPassword(pszGamePassword);
else
DvlNet_ClearPassword();
*playerID = dvlnet_inst->create(pszGameName);
return *playerID != -1;
const int createdPlayerId = dvlnet_inst->create(pszGameName);
if (createdPlayerId == -1)
return false;
*playerID = createdPlayerId;
if (!IsLoopback) {
std::string upperGameName = GameName;
std::transform(upperGameName.begin(), upperGameName.end(), upperGameName.begin(), ::toupper);
const char *privacy = GameIsPublic ? "public" : "private";
if (gameTemplateData != nullptr && gameTemplateSize >= static_cast<int>(sizeof(GameData))) {
const GameData *gameData = reinterpret_cast<const GameData *>(gameTemplateData);
LogInfo("Created {} {} multiplayer game '{}' (player id: {}, seed: {})",
privacy, ConnectionNames[provider], upperGameName, createdPlayerId,
FormatGameSeed(gameData->gameSeed));
} else {
LogInfo("Created {} {} multiplayer game '{}' (player id: {}, seed unavailable)",
privacy, ConnectionNames[provider], upperGameName, createdPlayerId);
}
}
return true;
}
bool SNetJoinGame(char *pszGameName, char *pszGamePassword, int *playerID)
@ -171,8 +197,12 @@ bool SNetJoinGame(char *pszGameName, char *pszGamePassword, int *playerID)
DvlNet_SetPassword(pszGamePassword);
else
DvlNet_ClearPassword();
*playerID = dvlnet_inst->join(pszGameName);
return *playerID != -1;
const int joinedPlayerId = dvlnet_inst->join(pszGameName);
if (joinedPlayerId == -1)
return false;
*playerID = joinedPlayerId;
// Join message with seed will be logged in NetInit after game data is synchronized
return true;
}
/**

10
Source/storm/storm_net.hpp

@ -10,6 +10,8 @@
namespace devilution {
using net::leaveinfo_t;
enum conn_type : uint8_t {
SELCONN_ZT,
SELCONN_TCP,
@ -23,6 +25,7 @@ enum event_type : uint8_t {
};
extern const char *ConnectionNames[];
extern int provider;
struct _SNETCAPS {
uint32_t size;
@ -53,9 +56,6 @@ struct DvlNetLatencies {
#define PS_TURN_ARRIVED 0x20000
#define PS_ACTIVE 0x40000
#define LEAVE_ENDING 0x40000004
#define LEAVE_DROP 0x40000006
bool SNetCreateGame(const char *pszGameName, const char *pszGamePassword, char *GameTemplateData, int GameTemplateSize, int *playerID);
bool SNetDestroy();
@ -68,7 +68,7 @@ bool SNetDestroy();
*
* Returns true if the function was called successfully and false otherwise.
*/
bool SNetDropPlayer(uint8_t playerid, uint32_t flags);
bool SNetDropPlayer(uint8_t playerid, leaveinfo_t flags);
/* SNetGetTurnsInTransit @ 115
*
@ -92,7 +92,7 @@ bool SNetJoinGame(char *gameName, char *gamePassword, int *playerid);
*
* Returns true if the function was called successfully and false otherwise.
*/
bool SNetLeaveGame(int type);
bool SNetLeaveGame(leaveinfo_t type);
bool SNetReceiveMessage(uint8_t *senderplayerid, void **data, size_t *databytes);
bool SNetReceiveTurns(int arraysize, char **arraydata, size_t *arraydatabytes, uint32_t *arrayplayerstatus);

28
test/multi_logging_test.cpp

@ -0,0 +1,28 @@
#include <gtest/gtest.h>
#include "multi.h"
namespace devilution {
TEST(MultiplayerLogging, NormalExitReason)
{
EXPECT_EQ("normal exit", DescribeLeaveReason(net::leaveinfo_t::LEAVE_EXIT));
}
TEST(MultiplayerLogging, DiabloEndingReason)
{
EXPECT_EQ("Diablo defeated", DescribeLeaveReason(net::leaveinfo_t::LEAVE_ENDING));
}
TEST(MultiplayerLogging, DropReason)
{
EXPECT_EQ("connection timeout", DescribeLeaveReason(net::leaveinfo_t::LEAVE_DROP));
}
TEST(MultiplayerLogging, CustomReasonCode)
{
constexpr net::leaveinfo_t CustomCode = static_cast<net::leaveinfo_t>(0xDEADBEEF);
EXPECT_EQ("code 0xDEADBEEF", DescribeLeaveReason(CustomCode));
}
} // namespace devilution
Loading…
Cancel
Save