diff --git a/lib/netplay/netplay.cpp b/lib/netplay/netplay.cpp
index 805c58a86..35a6356c8 100644
--- a/lib/netplay/netplay.cpp
+++ b/lib/netplay/netplay.cpp
@@ -2604,7 +2604,7 @@ static void LogChatMsg(uint8_t msgType, const NetMessage& message, uint8_t sende
 
 ///////////////////////////////////////////////////////////////////////////
 // Check if a message is a system message
-static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t *type)
+static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t *type, bool& stats)
 {
 	if (*type == NET_SECURED_NET_MESSAGE)
 	{
@@ -2859,6 +2859,7 @@ static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t *type)
 		{
 			recvMultiStats(playerQueue);
 			netPlayersUpdated = true;
+			stats = true;
 			break;
 		}
 	case NET_PLAYER_INFO:
@@ -3135,7 +3136,7 @@ static void NETcheckPlayers()
 // Receive a message over the current connection. We return true if there
 // is a message for the higher level code to process, and false otherwise.
 // We should not block here.
-bool NETrecvNet(NETQUEUE *queue, uint8_t *type)
+bool NETrecvNet(NETQUEUE *queue, uint8_t *type, bool& stats)
 {
 	if (!NetPlay.bComms)
 	{
@@ -3206,7 +3207,7 @@ checkMessages:
 		while (NETisMessageReady(*queue))
 		{
 			*type = NETgetMessage(*queue)->type();
-			if (!NETprocessSystemMessage(*queue, type))
+			if (!NETprocessSystemMessage(*queue, type, stats))
 			{
 				return true;  // We couldn't process the message, let the caller deal with it..
 			}
diff --git a/lib/netplay/netplay.h b/lib/netplay/netplay.h
index 9543036b8..4ec9a0406 100644
--- a/lib/netplay/netplay.h
+++ b/lib/netplay/netplay.h
@@ -363,7 +363,7 @@ enum class ConnectionProviderType : uint8_t;
 int NETinit(ConnectionProviderType pt);
 bool NETsend(NETQUEUE queue, NetMessage const& message);   ///< send to player, or broadcast if player == NET_ALL_PLAYERS.
 void NETsendProcessDelayedActions();
-WZ_DECL_NONNULL(1, 2) bool NETrecvNet(NETQUEUE *queue, uint8_t *type);        ///< recv a message from the net queues if possible.
+WZ_DECL_NONNULL(1, 2) bool NETrecvNet(NETQUEUE *queue, uint8_t *type, bool& stats);        ///< recv a message from the net queues if possible.
 WZ_DECL_NONNULL(1, 2) bool NETrecvGame(NETQUEUE *queue, uint8_t *type);       ///< recv a message from the game queues which is sceduled to execute by time, if possible.
 void NETflush();                                                              ///< Flushes any data stuck in compression buffers.
 
diff --git a/src/multiint.cpp b/src/multiint.cpp
index 691426324..f47773ce9 100644
--- a/src/multiint.cpp
+++ b/src/multiint.cpp
@@ -133,6 +133,7 @@
 #include "activity.h"
 #include <algorithm>
 #include <set>
+#include <iostream>
 #include "3rdparty/gsl_finally.h"
 
 #define MAP_PREVIEW_DISPLAY_TIME 2500	// number of milliseconds to show map in preview
@@ -6765,8 +6766,9 @@ WzMultiplayerOptionsTitleUI::MultiMessagesResult WzMultiplayerOptionsTitleUI::fr
 	NETQUEUE queue;
 	uint8_t type;
 	bool ignoredMessage = false;
+	bool stats = false;
 
-	while (NETrecvNet(&queue, &type))
+	while (NETrecvNet(&queue, &type, stats))
 	{
 		if (!shouldProcessMessage(queue, type))
 		{
@@ -7236,6 +7238,26 @@ WzMultiplayerOptionsTitleUI::MultiMessagesResult WzMultiplayerOptionsTitleUI::fr
 		NETpop(queue);
 	}
 
+	if (NetPlay.isHost) {
+		static std::chrono::steady_clock::time_point before;
+		static bool got_stats = false;
+		if (stats) {
+			std::cout << "xxxxxxxxxxxxxxxx received" << std::endl;
+			if (!got_stats) {
+				got_stats = true;
+				before = std::chrono::steady_clock::now();
+			}
+		} else if (got_stats) {
+			if (std::chrono::steady_clock::now() - before > std::chrono::seconds(1)) {
+				std::cout << "xxxxxxxxxxxxxxxx delayed send" << std::endl;
+				got_stats = false;
+				for (uint32_t playerIndex = 0; playerIndex < MAX_CONNECTED_PLAYERS; ++playerIndex)
+					if (playerIndex != NetPlay.hostPlayer && *NetPlay.players[playerIndex].name)
+						sendMultiStats(playerIndex);
+			}
+		}
+	}
+
 	const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
 	autoLagKickRoutine(now);
 	autoLobbyNotReadyKickRoutine(now);
diff --git a/src/multiplay.cpp b/src/multiplay.cpp
index 7f7549a41..18db5384e 100644
--- a/src/multiplay.cpp
+++ b/src/multiplay.cpp
@@ -1459,8 +1459,9 @@ bool recvMessage()
 {
 	NETQUEUE queue;
 	uint8_t type;
+	bool stats;
 
-	while (NETrecvNet(&queue, &type) || NETrecvGame(&queue, &type))          // for all incoming messages.
+	while (NETrecvNet(&queue, &type, stats) || NETrecvGame(&queue, &type))          // for all incoming messages.
 	{
 		bool processedMessage1 = false;
 		bool processedMessage2 = false;
diff --git a/src/multistat.cpp b/src/multistat.cpp
index 7167d866a..07d2e2b20 100644
--- a/src/multistat.cpp
+++ b/src/multistat.cpp
@@ -45,6 +45,8 @@
 #include <utility>
 #include <memory>
 #include <chrono>
+#include <fstream>
+#include <iostream>
 #include <SQLiteCpp/SQLiteCpp.h>
 
 // ////////////////////////////////////////////////////////////////////////////
@@ -211,6 +213,101 @@ static bool sendMultiStatsInternal(uint32_t playerIndex, optional<uint32_t> reci
 		pStatsToSend = &zeroStats;
 	}
 
+	if (NetPlay.isHost) {
+		static const char* env = std::getenv("WARZONE2100_RATING_DATA_DIR");
+		if(env) {
+			static const std::filesystem::path dir = env;
+			static const auto json_ip = nlohmann::json::parse(std::ifstream(dir / "ip.json"));
+			static const auto json_elo = nlohmann::json::parse(std::ifstream(dir / "elo.json"));
+			auto public_key = base64Encode(pStatsToSend->identity.toBytes(EcKey::Privacy::Public));
+			if (!public_key.empty()) {
+				const auto old_public_key = public_key;
+				const std::string ip = NetPlay.players[playerIndex].IPtextAddress;
+				std::string name = NetPlay.players[playerIndex].name;
+				auto old_name = name;
+				auto it = json_ip["publicKeys"].find(public_key);
+				if (it != json_ip["publicKeys"].end()) {
+					public_key = (*it)[0]["publicKey"].get<std::string>();
+					int count = 0, total_count = 0;
+					for(auto entry : (*it)) {
+						total_count += entry["count"].get<int>();
+						if (entry["publicKey"].get<std::string>() == old_public_key)
+							count = entry["count"].get<int>();
+					}
+					if (count * 10 < total_count) {
+						std::cout << "xxxxxxxxxxxxxxxx send count rename " << count << ' ' << total_count << ' ' << old_name << ' ' << old_public_key << ' ' << ip << std::endl;
+						name = (*it)[0]["name"].get<std::string>();
+					} else std::cout << "xxxxxxxxxxxxxxxx send count no rename " << count << ' ' << total_count << ' ' << old_name << ' ' << old_public_key << ' ' << ip << std::endl;
+				} else {
+					it = json_ip["ips"].find(ip);
+					if (it != json_ip["ips"].end()) {
+						std::cout << "xxxxxxxxxxxxxxxx send ip rename " << old_name << ' ' << old_public_key << ' ' << ip << std::endl;
+						public_key = (*it)[0]["publicKey"].get<std::string>();
+						name = (*it)[0]["name"].get<std::string>();
+					} else std::cout << "xxxxxxxxxxxxxxxx send not found " << old_name << ' ' << old_public_key << ' ' << ip << std::endl;
+				}
+				if (public_key != old_public_key) {
+					if (name != old_name) {
+						std::cout << "xxxxxxxxxxxxxxxx send rename and public key " << old_name << " => " << name << ' ' << old_public_key << " => " << public_key << ' ' << ip << std::endl;
+						NETchangePlayerName(playerIndex, name.data());
+					} else std::cout << "xxxxxxxxxxxxxxxx send no rename and public key " << old_name << ' ' << old_public_key << " => " << public_key << ' ' << ip << std::endl;
+				} else std::cout << "xxxxxxxxxxxxxxxx send no change " << old_name << ' ' << old_public_key << ' ' << ip << std::endl;
+				uint32_t played, wins, losses, totalKills;
+				it = json_elo.find(public_key);
+				if (it == json_elo.end()) {
+					played = wins = losses = totalKills = 0;
+					std::cout << "xxxxxxxxxxxxxxxx send unrated " << name << ' ' << public_key << ' ' << ip << std::endl;
+				} else {
+					const auto scale = (*it)["scale"].get<float>();
+					const auto rank = (*it)["rank"].get<unsigned int>();
+					std::cout << "xxxxxxxxxxxxxxxx send rated " << rank << ' ' << scale << ' ' << name << ' ' << public_key << ' ' << ip << std::endl;
+					#if 0 // excerpt from lobbyplayerrow.cpp displayPlayer
+						bool dummy = stat.played < 5;
+						uint8_t star[3] = {0, 0, 0};
+						uint8_t medal = 0;
+						// star 1 total droid kills
+						star[0] = stat.totalKills > 600? 1 : stat.totalKills > 300? 2 : stat.totalKills > 150? 3 : 0;
+						// star 2 games played
+						star[1] = stat.played > 200? 1 : stat.played > 100? 2 : stat.played > 50? 3 : 0;
+						// star 3 games won.
+						star[2] = stat.wins > 80? 1 : stat.wins > 40? 2 : stat.wins > 10? 3 : 0;
+						// medals.
+						medal = stat.wins >= 24 && stat.wins > 8 * stat.losses? 1 : stat.wins >= 12 && stat.wins > 4 * stat.losses? 2 : stat.wins >= 6 && stat.wins > 2 * stat.losses? 3 : 0;
+					#endif
+					if (scale < 0.4) {
+						if (scale < 0.1) totalKills = 0;
+						else if (scale < 0.2) totalKills = 151;
+						else if (scale < 0.3) totalKills = 301;
+						else totalKills = 601;
+						played = 5;
+						wins = 10;
+					} else {
+						totalKills = 601;
+						if (scale < 0.7) {
+							if (scale < 0.5) played = 51;
+							else if (scale < 0.6) played = 101;
+							else played = 201;
+							wins = 10;
+						} else {
+							played = 201;
+							if (scale < 0.8) wins = 11;
+							else if (scale < 0.9) wins = 41;
+							else wins = 81;
+						}
+					}
+					if (rank > 6) losses = wins;
+					else if (rank > 3) losses = wins / 2 - 1;
+					else if (rank > 1) losses = wins / 4 - 1;
+					else losses = wins / 8 - 1;
+				}
+				pStatsToSend->played = played;
+				pStatsToSend->wins = wins;
+				pStatsToSend->losses = losses;
+				pStatsToSend->totalKills = totalKills;
+			}
+		}
+	}
+
 	NETuint32_t(w, pStatsToSend->played);
 	NETuint32_t(w, pStatsToSend->wins);
 	NETuint32_t(w, pStatsToSend->losses);
