2727#include " Modules/Server.hpp"
2828#include " Scheduler.hpp"
2929#include " Utils.hpp"
30+ #include " Utils/ed25519/ed25519.h"
3031
3132#define SPEEDRUN_PACKET_TYPE " srtimer"
3233#define SYNC_INTERVAL 1 .0f // Sync every second, just in case
@@ -39,6 +40,8 @@ enum PacketType {
3940 STOP,
4041 SPLIT,
4142 RESET,
43+ ID_REQUEST, // Orange requests speedrun ID
44+ ID_RESPONSE, // Blue responds with speedrun ID
4245};
4346
4447// TimerAction {{{
@@ -118,6 +121,9 @@ static struct
118121
119122 std::vector<std::string> visitedMaps;
120123 std::string lastMap;
124+
125+ uint8_t speedrunId[16 ]; // 128-bit unique identifier
126+ bool hasSpeedrunId; // Whether ID has been set
121127} g_speedrun;
122128
123129static std::map<std::string, int > g_activeRun;
@@ -126,6 +132,8 @@ static std::vector<std::map<std::string, int>> g_runs;
126132bool g_timePauses = false ;
127133
128134static void handleCoopPacket (const void *data, size_t size);
135+ static void sendIdRequest ();
136+ static void sendIdResponse ();
129137
130138ON_INIT {
131139 g_timerInterface = new TimerInterface ();
@@ -160,15 +168,24 @@ static bool g_coopActuallyReady = false;
160168static int g_coopLastReadyTick = 0 ;
161169
162170static void handleCoopPacket (const void *data, size_t size) {
163- if (!engine->IsOrange ()) return ;
164-
165171 char *data_ = (char *)data;
166172
167173 if (size < 5 ) return ;
168174
169175 PacketType t = (PacketType)data_[0 ];
170176 int tick = *(int *)(data_ + 1 );
171177
178+ // Blue handles ID_REQUEST, Orange handles everything else
179+ if (t == PacketType::ID_REQUEST) {
180+ if (!engine->IsOrange () && g_speedrun.hasSpeedrunId ) {
181+ sendIdResponse ();
182+ }
183+ return ;
184+ }
185+
186+ // All other packets are Orange-only
187+ if (!engine->IsOrange ()) return ;
188+
172189 g_coopLastSyncTick = tick;
173190 g_coopLastSyncEngineTick = engine->GetTick ();
174191
@@ -179,6 +196,10 @@ static void handleCoopPacket(const void *data, size_t size) {
179196 break ;
180197 case PacketType::START:
181198 SpeedrunTimer::Start ();
199+ // Orange requests the speedrun ID
200+ if (g_partnerHasSAR) {
201+ sendIdRequest ();
202+ }
182203 break ;
183204 case PacketType::PAUSE:
184205 SpeedrunTimer::Pause ();
@@ -196,6 +217,13 @@ static void handleCoopPacket(const void *data, size_t size) {
196217 case PacketType::RESET:
197218 SpeedrunTimer::Reset ();
198219 break ;
220+ case PacketType::ID_RESPONSE:
221+ // Orange receives speedrun ID
222+ if (size >= 21 ) {
223+ memcpy (g_speedrun.speedrunId , data_ + 5 , 16 );
224+ g_speedrun.hasSpeedrunId = true ;
225+ }
226+ break ;
199227 }
200228}
201229
@@ -309,6 +337,32 @@ static void sendCoopPacket(PacketType t, std::string *splitName = NULL, int newS
309337 free (buf);
310338}
311339
340+ void SpeedrunTimer::WriteIdToDemo () {
341+ if (!engine->demorecorder ->isRecordingDemo ) return ;
342+ if (!g_speedrun.hasSpeedrunId ) return ;
343+
344+ uint8_t data[17 ];
345+ data[0 ] = 0x13 ; // Demo data type for speedrun identifier
346+ memcpy (data + 1 , g_speedrun.speedrunId , 16 );
347+
348+ engine->demorecorder ->RecordData (data, sizeof (data));
349+ }
350+
351+ static void sendIdRequest () {
352+ char buf[5 ];
353+ buf[0 ] = (char )PacketType::ID_REQUEST;
354+ *(int *)(buf + 1 ) = getCurrentTick ();
355+ NetMessage::SendMsg (SPEEDRUN_PACKET_TYPE, buf, 5 );
356+ }
357+
358+ static void sendIdResponse () {
359+ char buf[21 ];
360+ buf[0 ] = (char )PacketType::ID_RESPONSE;
361+ *(int *)(buf + 1 ) = getCurrentTick ();
362+ memcpy (buf + 5 , g_speedrun.speedrunId , 16 );
363+ NetMessage::SendMsg (SPEEDRUN_PACKET_TYPE, buf, 21 );
364+ }
365+
312366int SpeedrunTimer::GetOffsetTicks () {
313367 const char *offset = sar_speedrun_offset.GetString ();
314368
@@ -511,6 +565,19 @@ void SpeedrunTimer::Start() {
511565 g_speedrun.lastMap = map;
512566 g_speedrun.visitedMaps .push_back (map);
513567
568+ // Generate unique speedrun ID
569+ if (ed25519_create_seed (g_speedrun.speedrunId ) != 0 ) {
570+ // Fallback: use timestamp + random if crypto fails
571+ auto now = std::chrono::system_clock::now ().time_since_epoch ();
572+ uint64_t timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now).count ();
573+ *(uint64_t *)(g_speedrun.speedrunId ) = timestamp;
574+ for (int i = 8 ; i < 16 ; i++) {
575+ g_speedrun.speedrunId [i] = (uint8_t )(Math::RandomNumber (0 , 255 ));
576+ }
577+ }
578+ g_speedrun.hasSpeedrunId = true ;
579+ SpeedrunTimer::WriteIdToDemo (); // Write to current demo if recording
580+
514581 sendCoopPacket (PacketType::START);
515582 if (!sar_mtrigger_legacy.GetBool ()) {
516583 toastHud.AddToast (SPEEDRUN_TOAST_TAG, " Speedrun started!" );
@@ -793,6 +860,10 @@ void SpeedrunTimer::Reset(bool requested) {
793860 g_speedrun.splits .clear ();
794861 g_speedrun.visitedMaps .clear ();
795862
863+ // Clear speedrun ID
864+ g_speedrun.hasSpeedrunId = false ;
865+ memset (g_speedrun.speedrunId , 0 , 16 );
866+
796867 if (networkManager.isConnected ) {
797868 networkManager.splitTicks = -1 ;
798869 }
0 commit comments