mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge pull request #4179 from janisozaur/network-checksum-sprites
Send a checksum of all the sprites occasionally in multiplayer for better desync detection.
This commit is contained in:
commit
17c5178dec
|
@ -586,7 +586,10 @@ bool Network::CheckSRAND(uint32 tick, uint32 srand0)
|
|||
|
||||
if (tick == server_srand0_tick) {
|
||||
server_srand0_tick = 0;
|
||||
if (srand0 != server_srand0) {
|
||||
// Check that the server and client sprite hashes match
|
||||
const bool sprites_mismatch = server_sprite_hash[0] != '\0' && strcmp(sprite_checksum(), server_sprite_hash);
|
||||
// Check PRNG values and sprite hashes, if exist
|
||||
if ((srand0 != server_srand0) || sprites_mismatch) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1081,6 +1084,22 @@ void Network::Server_Send_TICK()
|
|||
last_tick_sent_time = SDL_GetTicks();
|
||||
std::unique_ptr<NetworkPacket> packet = std::move(NetworkPacket::Allocate());
|
||||
*packet << (uint32)NETWORK_COMMAND_TICK << (uint32)gCurrentTicks << (uint32)gScenarioSrand0;
|
||||
uint32 flags = 0;
|
||||
// Simple counter which limits how often a sprite checksum gets sent.
|
||||
// This can get somewhat expensive, so we don't want to push it every tick in release,
|
||||
// but debug version can check more often.
|
||||
static int checksum_counter = 0;
|
||||
checksum_counter++;
|
||||
if (checksum_counter >= 100) {
|
||||
checksum_counter = 0;
|
||||
flags |= NETWORK_TICK_FLAG_CHECKSUMS;
|
||||
}
|
||||
// Send flags always, so we can understand packet structure on the other end,
|
||||
// and allow for some expansion.
|
||||
*packet << flags;
|
||||
if (flags & NETWORK_TICK_FLAG_CHECKSUMS) {
|
||||
packet->WriteString(sprite_checksum());
|
||||
}
|
||||
SendPacketToClients(*packet);
|
||||
}
|
||||
|
||||
|
@ -1739,10 +1758,23 @@ void Network::Server_Handle_GAMECMD(NetworkConnection& connection, NetworkPacket
|
|||
void Network::Client_Handle_TICK(NetworkConnection& connection, NetworkPacket& packet)
|
||||
{
|
||||
uint32 srand0;
|
||||
packet >> server_tick >> srand0;
|
||||
uint32 flags;
|
||||
// Note: older server version may not advertise flags at all.
|
||||
// NetworkPacket will return 0, if trying to read past end of buffer,
|
||||
// so flags == 0 is expected in such cases.
|
||||
packet >> server_tick >> srand0 >> flags;
|
||||
if (server_srand0_tick == 0) {
|
||||
server_srand0 = srand0;
|
||||
server_srand0_tick = server_tick;
|
||||
server_sprite_hash[0] = '\0';
|
||||
if (flags & NETWORK_TICK_FLAG_CHECKSUMS)
|
||||
{
|
||||
const char* text = packet.ReadString();
|
||||
if (text != nullptr)
|
||||
{
|
||||
safe_strcpy(server_sprite_hash, text, sizeof(server_sprite_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ extern "C" {
|
|||
#include "../game.h"
|
||||
#include "../platform/platform.h"
|
||||
#include "../localisation/string_ids.h"
|
||||
#include <openssl/evp.h>
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
@ -77,6 +78,10 @@ extern "C" {
|
|||
#include "NetworkUser.h"
|
||||
#include "TcpSocket.h"
|
||||
|
||||
enum {
|
||||
NETWORK_TICK_FLAG_CHECKSUMS = 1 << 0,
|
||||
};
|
||||
|
||||
class Network
|
||||
{
|
||||
public:
|
||||
|
@ -191,6 +196,7 @@ private:
|
|||
uint32 server_tick = 0;
|
||||
uint32 server_srand0 = 0;
|
||||
uint32 server_srand0_tick = 0;
|
||||
char server_sprite_hash[EVP_MAX_MD_SIZE + 1];
|
||||
uint8 player_id = 0;
|
||||
std::list<std::unique_ptr<NetworkConnection>> client_connection_list;
|
||||
std::multiset<GameCommand> game_command_queue;
|
||||
|
|
|
@ -63,6 +63,11 @@ bool gOpenRCT2Headless = false;
|
|||
bool gOpenRCT2ShowChangelog;
|
||||
bool gOpenRCT2SilentBreakpad;
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
// OpenSSL's message digest context used for calculating sprite checksums
|
||||
EVP_MD_CTX *gHashCTX = NULL;
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
/** If set, will end the OpenRCT2 game loop. Intentially private to this module so that the flag can not be set back to 0. */
|
||||
int _finished;
|
||||
|
||||
|
@ -176,6 +181,11 @@ bool openrct2_initialise()
|
|||
{
|
||||
utf8 userPath[MAX_PATH];
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
gHashCTX = EVP_MD_CTX_create();
|
||||
assert(gHashCTX != NULL);
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
platform_resolve_openrct_data_path();
|
||||
platform_resolve_user_data_path();
|
||||
platform_get_user_directory(userPath, NULL);
|
||||
|
@ -336,6 +346,9 @@ void openrct2_dispose()
|
|||
language_close_all();
|
||||
rct2_dispose();
|
||||
config_release();
|
||||
#ifndef DISABLE_NETWORK
|
||||
EVP_MD_CTX_destroy(gHashCTX);
|
||||
#endif // DISABLE_NETWORK
|
||||
platform_free();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include "common.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
#include <openssl/evp.h>
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
enum {
|
||||
STARTUP_ACTION_INTRO,
|
||||
STARTUP_ACTION_TITLE,
|
||||
|
@ -39,6 +43,10 @@ extern utf8 gCustomPassword[MAX_PATH];
|
|||
extern bool gOpenRCT2Headless;
|
||||
extern bool gOpenRCT2ShowChangelog;
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
extern EVP_MD_CTX *gHashCTX;
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
extern int gNetworkStart;
|
||||
extern char gNetworkStartHost[128];
|
||||
|
|
|
@ -168,6 +168,50 @@ void game_command_reset_sprites(int* eax, int* ebx, int* ecx, int* edx, int* esi
|
|||
*ebx = 0;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_NETWORK
|
||||
|
||||
static unsigned char _spriteChecksum[EVP_MAX_MD_SIZE + 1];
|
||||
|
||||
const char * sprite_checksum()
|
||||
{
|
||||
if (EVP_DigestInit_ex(gHashCTX, EVP_sha1(), NULL) <= 0)
|
||||
{
|
||||
openrct2_assert(false, "Failed to initialise SHA1 engine");
|
||||
}
|
||||
for (size_t i = 0; i < MAX_SPRITES; i++)
|
||||
{
|
||||
rct_sprite *sprite = get_sprite(i);
|
||||
if (sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL)
|
||||
{
|
||||
if (EVP_DigestUpdate(gHashCTX, sprite, sizeof(rct_sprite)) <= 0)
|
||||
{
|
||||
openrct2_assert(false, "Failed to update digest");
|
||||
}
|
||||
}
|
||||
}
|
||||
unsigned char localhash[EVP_MAX_MD_SIZE + 1];
|
||||
unsigned int size = sizeof(localhash);
|
||||
EVP_DigestFinal(gHashCTX, localhash, &size);
|
||||
assert(size <= sizeof(localhash));
|
||||
localhash[sizeof(localhash) - 1] = '\0';
|
||||
char *x = (char *)_spriteChecksum;
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
{
|
||||
sprintf(x, "%02x", localhash[i]);
|
||||
x += 2;
|
||||
}
|
||||
*x = '\0';
|
||||
return (char *)_spriteChecksum;
|
||||
}
|
||||
#else
|
||||
|
||||
const char * sprite_checksum()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif // DISABLE_NETWORK
|
||||
|
||||
/**
|
||||
* Clears all the unused sprite memory to zero. Probably so that it can be compressed better when saving.
|
||||
* rct2: 0x0069EBA4
|
||||
|
|
|
@ -441,4 +441,6 @@ void crashed_vehicle_particle_update(rct_crashed_vehicle_particle *particle);
|
|||
void crash_splash_create(int x, int y, int z);
|
||||
void crash_splash_update(rct_crash_splash *splash);
|
||||
|
||||
const char *sprite_checksum();
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue