Skip to content

Commit 68d5667

Browse files
authored
feat: unique speedrun identifier (#355)
* Added Speedrun-Identifier * Fixed speedrun identifier not appearing in first demo
1 parent 4703308 commit 68d5667

4 files changed

Lines changed: 76 additions & 2 deletions

File tree

src/Features/Speedrun/SpeedrunTimer.cpp

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
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

123129
static std::map<std::string, int> g_activeRun;
@@ -126,6 +132,8 @@ static std::vector<std::map<std::string, int>> g_runs;
126132
bool g_timePauses = false;
127133

128134
static void handleCoopPacket(const void *data, size_t size);
135+
static void sendIdRequest();
136+
static void sendIdResponse();
129137

130138
ON_INIT {
131139
g_timerInterface = new TimerInterface();
@@ -160,15 +168,24 @@ static bool g_coopActuallyReady = false;
160168
static int g_coopLastReadyTick = 0;
161169

162170
static 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+
312366
int 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
}

src/Features/Speedrun/SpeedrunTimer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ namespace SpeedrunTimer {
3434
bool IsRunning();
3535

3636
void RecordIncompleteSummary();
37+
void WriteIdToDemo();
3738

3839
void OnLoad();
3940
void CategoryChanged();

src/Modules/EngineDemoPlayer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ std::string EngineDemoPlayer::GetLevelName() {
173173
// 0x10: queued commands
174174
// 0x11: VPK internal checksums
175175
// 0x12: incomplete speedrun summary
176+
// 0x13: speedrun identifier
176177
void EngineDemoPlayer::CustomDemoData(char *data, size_t length) {
177178
if (data[0] == 0x03 || data[0] == 0x04) { // Entity input data
178179
std::optional<int> slot;

src/Modules/EngineDemoRecorder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ DETOUR(EngineDemoRecorder::SetSignonState, int state) {
170170
if (state == SIGNONSTATE_FULL && needToRecordInitialVals) {
171171
needToRecordInitialVals = false;
172172
RecordTimestamp();
173+
SpeedrunTimer::WriteIdToDemo(); // Write speedrun ID to every demo segment
173174
RecordQueuedCommands();
174175
SpeedrunTimer::RecordIncompleteSummary();
175176
engine->ExecuteCommand("echo \"SAR " SAR_VERSION " (Built " SAR_BUILT ")\"", true);

0 commit comments

Comments
 (0)