shadNet: report the client version after signing in

A shadNet server with clientversions.cfg rules signs out a client that never
sends SetClientVersion, then refuses its logins with LoginError for a while.
We sent nothing, so on the public server the game went offline a few seconds
after signing in and the next logins failed.

- send SetClientVersion (command 13) right after the login reply; the string
  is "shadPS4 bbport", BB_CLIENT_VERSION overrides it
- log the ClientRejected notification (19) with the server's message
- keep the SetClientVersion reply away from OnScoreReply

Thanks: Vorshim92, in 7 languages.

Co-Authored-By: Stefano Scalfari <[email protected]>
This commit is contained in:
ayozetrandStefano Scalfari committed 2026-10-14 12:08:54 +01:00
1 parent 4f3187843f
commit 2bcc093bd9
5 files changed
+64

No files matched your search

+35
View File
@@ -4,6 +4,7 @@
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <thread>
#include "client.h"
@@ -537,6 +538,21 @@ bool ShadNetClient::RequestServerFeatures() {
return true;
}
// BB_CLIENT_VERSION replaces the default, for a server whose clientversions.cfg expects
// another format.
std::string ShadNetClient::BuildVersionString() {
if (const char* version = std::getenv("BB_CLIENT_VERSION"); version && *version)
return version;
return "shadPS4 bbport";
}
u64 ShadNetClient::ReportClientVersion() {
shadnet::SetClientVersionRequest req;
req.set_version(BuildVersionString());
LOG_INFO(ShadNet, "Reporting client version '{}'", req.version());
return SubmitRequest(CommandType::SetClientVersion, MakeProtoPayload(req));
}
// Packet dispatch
void ShadNetClient::DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id,
@@ -553,6 +569,12 @@ void ShadNetClient::DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id,
case CommandType::GetServerFeatures:
HandleServerFeaturesReply(payload);
break;
case CommandType::SetClientVersion:
// Nobody waits for this reply; a refusal arrives as a ClientRejected notification.
if (!payload.empty() && static_cast<ErrorType>(payload[0]) != ErrorType::NoError)
LOG_WARNING(ShadNet, "SetClientVersion returned error={}",
static_cast<int>(payload[0]));
break;
default:
if (onAsyncReply) {
// Every reply body starts with an ErrorType byte.
@@ -623,6 +645,9 @@ void ShadNetClient::HandleLoginReply(const std::vector<u8>& payload) {
LOG_INFO(ShadNet, "Logged in npid='{}' userId={} friends={}", m_npid, m_user_id,
m_friends.size());
// First, before the server's version timeout runs out.
ReportClientVersion();
const u64 pkt_id = m_pkt_counter.fetch_add(1);
std::vector<u8> empty_payload;
std::vector<u8> pkt = BuildPacket(CommandType::GetToken, pkt_id, empty_payload);
@@ -911,6 +936,16 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
onWebApiPushEvent(n);
break;
}
case NotificationType::ClientRejected: {
shadnet::NotifyClientRejected pb;
if (!pb.ParseFromString(blob)) {
LOG_WARNING(ShadNet, "ClientRejected parse error");
break;
}
LOG_ERROR(ShadNet, "Server rejected this client (reason={}, version='{}'): {}",
pb.reason(), pb.version(), pb.message());
break;
}
default:
LOG_DEBUG(ShadNet, "Unknown notification type {}", cmd_raw);
break;
+5
View File
@@ -67,6 +67,7 @@ enum class CommandType : u16 {
AddBlock = 10,
RemoveBlock = 11,
GetServerFeatures = 12,
SetClientVersion = 13,
GetBoardInfos = 30,
RecordScore = 31,
RecordScoreData = 32,
@@ -105,6 +106,7 @@ enum class NotificationType : u16 {
RoomEvent = 10,
RoomMessage = 11,
WebApiPushEvent = 17, // Generic NP WebApi push event
ClientRejected = 19, // The server is signing this client out and says why
};
enum class ErrorType : uint8_t {
@@ -328,6 +330,9 @@ private:
void HandleServerFeaturesReply(const std::vector<u8>& payload);
void HandleNotification(u16 cmd_raw, const std::vector<u8>& payload);
bool RequestServerFeatures();
// Tell the server which client this is; a shadNet server may deny clients that stay silent.
u64 ReportClientVersion();
static std::string BuildVersionString();
// Helper: read a u32-LE-prefixed proto blob from a byte vector at pos.
static std::string ExtractBlob(const std::vector<u8>& p, int pos);
+16
View File
@@ -29,6 +29,22 @@ message SetAppearOfflineRequest {
bool appear_offline = 1;
}
// Sent right after signing in. A server with clientversions.cfg rules signs out a client
// that reports nothing, or a version it denies.
message SetClientVersionRequest {
string version = 1;
}
// Notification 19: the server is signing this client out, then disconnects.
// reason: 1 = client version not accepted
// message: text for the player, from the rule that matched
// version: the version the server checked ("" = none reported)
message NotifyClientRejected {
uint32 reason = 1;
string message = 2;
string version = 3;
}
message ServerFeaturesReply {
bool matching2_enabled = 1;
}
+7
View File
@@ -595,6 +595,7 @@ EN = {
'Очистить кэш шейдеров?': 'Clear the shader cache?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': 'The game builds the shaders again: some stutter in each area for the first minutes. Saves and settings are not touched.',
'Названия трофеев на английском без ключа и звук при получении трофея': 'Trophy names in English without the key, and the sound when one is earned',
'Вход на публичный сервер shadNet без отключения': 'Staying signed in on the public shadNet server',
}
PT_BR = {
@@ -1244,6 +1245,7 @@ PT_BR = {
'Очистить кэш шейдеров?': 'Limpar o cache de shaders?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': 'O jogo gera os shaders de novo: pode haver travadas nos primeiros minutos de cada área. Saves e configurações não são afetados.',
'Названия трофеев на английском без ключа и звук при получении трофея': 'Nomes dos troféus em inglês sem a chave e o som ao conquistar um',
'Вход на публичный сервер shadNet без отключения': 'Continuar conectado no servidor público do shadNet',
}
@@ -1804,6 +1806,7 @@ ES = {
'Очистить кэш шейдеров?': '¿Borrar la caché de shaders?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': 'El juego volverá a generar los shaders: puede haber tirones los primeros minutos en cada zona. Las partidas y los ajustes no se tocan.',
'Названия трофеев на английском без ключа и звук при получении трофея': 'Los nombres de los trofeos en inglés sin la clave y el sonido al conseguir uno',
'Вход на публичный сервер shadNet без отключения': 'Seguir conectado en el servidor público de shadNet',
}
@@ -2364,6 +2367,7 @@ IT = {
'Очистить кэш шейдеров?': 'Svuotare la cache degli shader?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': 'Il gioco ricrea gli shader: possibili scatti nei primi minuti di ogni area. Salvataggi e impostazioni non vengono toccati.',
'Названия трофеев на английском без ключа и звук при получении трофея': 'I nomi dei trofei in inglese senza la chiave e il suono quando se ne ottiene uno',
'Вход на публичный сервер shadNet без отключения': 'Restare connessi al server pubblico di shadNet',
}
@@ -2924,6 +2928,7 @@ FR = {
'Очистить кэш шейдеров?': 'Vider le cache des shaders ?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': 'Le jeu reconstruit les shaders : quelques saccades les premières minutes dans chaque zone. Les sauvegardes et les réglages ne sont pas touchés.',
'Названия трофеев на английском без ключа и звук при получении трофея': 'Les noms des trophées en anglais sans la clé et le son à l’obtention d’un trophée',
'Вход на публичный сервер shadNet без отключения': 'Rester connecté au serveur public shadNet',
}
@@ -3484,6 +3489,7 @@ DE = {
'Очистить кэш шейдеров?': 'Shader-Cache leeren?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': 'Das Spiel erstellt die Shader neu: In den ersten Minuten jedes Gebiets kann es ruckeln. Spielstände und Einstellungen bleiben unberührt.',
'Названия трофеев на английском без ключа и звук при получении трофея': 'Trophäennamen auf Englisch ohne Schlüssel und der Ton beim Erhalt einer Trophäe',
'Вход на публичный сервер shadNet без отключения': 'Auf dem öffentlichen shadNet-Server angemeldet bleiben',
}
@@ -3987,6 +3993,7 @@ ZH_CN = {
'Очистить кэш шейдеров?': '清除着色器缓存?',
'Игра соберёт шейдеры заново: первые минуты в каждой зоне возможны рывки. Сохранения и настройки не затрагиваются.': '游戏会重新生成着色器:每个区域的前几分钟可能卡顿。存档和设置不受影响。',
'Названия трофеев на английском без ключа и звук при получении трофея': '无密钥时的英文奖杯名称与获得奖杯时的音效',
'Вход на публичный сервер shadNet без отключения': '在公共 shadNet 服务器上保持登录',
}
# Translations by language code; Russian is the key itself. Strings missing in a translation
+1
View File
@@ -626,6 +626,7 @@ THANKS = (
_github("andrecabralgit", "Звук игры на полной громкости"),
_github("Jgamrot", "Флажки и ползунки внутриигрового меню"),
_github("GerasimosKan", "Названия трофеев на английском без ключа и звук при получении трофея"),
_github("Vorshim92", "Вход на публичный сервер shadNet без отключения"),
)),
("Помощь и тесты", (
_github("willthehuman", "Подробные отчёты с журналами и проверка исправлений"),