Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/Checksum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#define READ_LE32(arr, i) \
(((uint32_t)arr[i + 0] << 0) | ((uint32_t)arr[i + 1] << 8) | ((uint32_t)arr[i + 2] << 16) | ((uint32_t)arr[i + 3] << 24))

static constexpr uint8_t SAR_MSG_VSCRIPT_RUNTIME_CHECKSUM = 0x14;

// clang-format off
static const uint32_t crcTable[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
Expand Down Expand Up @@ -507,6 +509,40 @@ static void addVpkInternalChecksum(const VpkInternalData &vpk) {
engine->demorecorder->RecordData(data.data(), data.size());
}

static size_t BoundedCStringLen(const char *str, size_t maxLen) {
if (!str) return 0;
size_t len = 0;
while (len < maxLen && str[len]) ++len;
return len;
}

void RecordRuntimeVscriptChecksum(const char *scriptName, const char *scriptData) {
if (!scriptName || !*scriptName || !scriptData || !*scriptData) return;

// Avoid an unbounded C-string search in case file data is not null-terminated.
constexpr size_t MAX_SCRIPT_SIZE = 8 * 1024 * 1024;
size_t scriptLen = BoundedCStringLen(scriptData, MAX_SCRIPT_SIZE);
if (scriptLen == 0) return;

uint32_t sum = 0;
if (scriptLen < MAX_SCRIPT_SIZE) {
sum = crc32(scriptData, scriptLen);
}

if (engine->demorecorder->isRecordingDemo && engine->demorecorder->GetTick() >= 0) {
size_t nameLen = strlen(scriptName);
size_t bufLen = nameLen + 6;
auto *buf = new uint8_t[bufLen];
buf[0] = SAR_MSG_VSCRIPT_RUNTIME_CHECKSUM;
*reinterpret_cast<uint32_t *>(buf + 1) = sum;
strcpy(reinterpret_cast<char *>(buf + 5), scriptName);
engine->demorecorder->RecordData(buf, bufLen);
delete[] buf;
} else {
engine->demorecorder->queuedVScriptChecksums.emplace_back(scriptName, sum);
}
}

void AddDemoVpkChecksums() {
if (g_vpkThread.joinable()) g_vpkThread.join();

Expand Down
1 change: 1 addition & 0 deletions src/Checksum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
bool AddDemoChecksum(const char *filename);
void AddDemoFileChecksums();
void AddDemoVpkChecksums();
void RecordRuntimeVscriptChecksum(const char *scriptName, const char *scriptData);
1 change: 1 addition & 0 deletions src/Modules.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
#include "Modules/Tier1.hpp"
#include "Modules/VGui.hpp"
#include "Modules/VPhysics.hpp"
#include "Modules/VScript.hpp"
1 change: 1 addition & 0 deletions src/Modules/EngineDemoPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ std::string EngineDemoPlayer::GetLevelName() {
// 0x11: VPK internal checksums
// 0x12: incomplete speedrun summary
// 0x13: speedrun identifier
// 0x14: runtime vscript checksum
void EngineDemoPlayer::CustomDemoData(char *data, size_t length) {
if (data[0] == 0x03 || data[0] == 0x04) { // Entity input data
std::optional<int> slot;
Expand Down
27 changes: 27 additions & 0 deletions src/Modules/EngineDemoRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,33 @@ static void RecordQueuedCommands() {
engine->demorecorder->queuedCommands.clear();
}

static void RecordQueuedVScriptChecksums() {
if (!engine->demorecorder->isRecordingDemo) return;
if (!engine->demorecorder->customDataReady) return;
if (engine->demorecorder->GetTick() < 0) return;

for (auto &queuedChecksum : engine->demorecorder->queuedVScriptChecksums) {
size_t nameLen = queuedChecksum.first.size();
size_t bufLen = nameLen + 6;
auto *buf = new uint8_t[bufLen];
buf[0] = 0x14;
*reinterpret_cast<uint32_t *>(buf + 1) = queuedChecksum.second;
strcpy(reinterpret_cast<char *>(buf + 5), queuedChecksum.first.c_str());
engine->demorecorder->RecordData(buf, bufLen);
delete[] buf;
}
engine->demorecorder->queuedVScriptChecksums.clear();
}

ON_EVENT(PRE_TICK) {
if (!engine->demorecorder->queuedVScriptChecksums.empty()) {
RecordQueuedVScriptChecksums();
}
}

ON_EVENT(SESSION_END) {
engine->demorecorder->queuedVScriptChecksums.clear();

if (*engine->demorecorder->m_bRecording && sar_autorecord.GetInt() == -1) {
engine->demorecorder->Stop();
}
Expand Down Expand Up @@ -172,6 +198,7 @@ DETOUR(EngineDemoRecorder::SetSignonState, int state) {
RecordTimestamp();
SpeedrunTimer::WriteIdToDemo(); // Write speedrun ID to every demo segment
RecordQueuedCommands();
RecordQueuedVScriptChecksums();
SpeedrunTimer::RecordIncompleteSummary();
engine->ExecuteCommand("echo \"SAR " SAR_VERSION " (Built " SAR_BUILT ")\"", true);
AddDemoFileChecksums();
Expand Down
3 changes: 3 additions & 0 deletions src/Modules/EngineDemoRecorder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "Utils.hpp"

#include <string>
#include <utility>
#include <vector>

// Ticks before demo autostop
#define DEMO_AUTOSTOP_DELAY 15
Expand All @@ -28,6 +30,7 @@ class EngineDemoRecorder : public Module {
int autorecordStartNum = 1;

std::vector<std::string> queuedCommands = {};
std::vector<std::pair<std::string, uint32_t>> queuedVScriptChecksums = {};

char coopRadialMenuLastPos[8];

Expand Down
38 changes: 38 additions & 0 deletions src/Modules/VScript.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "VScript.hpp"

#include "Checksum.hpp"
#include "Game.hpp"
#include "Hook.hpp"
#include "Offsets.hpp"
#include "SAR.hpp"

REDECL(VScript::CompileScript);

extern Hook g_VScriptCompileScriptHook;
DETOUR_T(int, VScript::CompileScript, const char *scriptData, const char *scriptName) {
if (scriptName && *scriptName) {
RecordRuntimeVscriptChecksum(scriptName, scriptData);
}

g_VScriptCompileScriptHook.Disable();
auto ret = VScript::CompileScript(thisptr, scriptData, scriptName);
g_VScriptCompileScriptHook.Enable();
return ret;
}
Hook g_VScriptCompileScriptHook(&VScript::CompileScript_Hook);

bool VScript::Init() {
VScript::CompileScript = (VScript::_CompileScript)Memory::Scan(this->Name(), Offsets::VScript_CompileScript);
if (!VScript::CompileScript) {
console->Warning("[sar] failed to find VScript_CompileScript\n");
return false;
}

g_VScriptCompileScriptHook.SetFunc(VScript::CompileScript);
return this->hasLoaded = true;
}

void VScript::Shutdown() {
}

VScript *vscript;
16 changes: 16 additions & 0 deletions src/Modules/VScript.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include "Module.hpp"
#include "Utils.hpp"

class VScript : public Module {
public:
// CVScriptGameSystem::CompileScript
DECL_DETOUR_T(int, CompileScript, const char *scriptData, const char *scriptName);

bool Init() override;
void Shutdown() override;
const char *Name() override { return MODULE("vscript"); }
};

extern VScript *vscript;
3 changes: 3 additions & 0 deletions src/Offsets/Portal 2 9568.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,9 @@ OFFSET_DEFAULT(Portal2PromoFlagsOff, 2, 1)
OFFSET_EMPTY(DestroyEnvironment)
OFFSET_EMPTY(GetActiveEnvironmentByIndex)

// Vscript
SIGSCAN_DEFAULT(VScript_CompileScript, "55 8B EC 83 EC 0C 56 57 8B 7D 08 8B F1 85 FF 0F 84 ? ? ? ? 80 3F 00 0F 84 ? ? ? ? 53 8B", "57 56 53 8B 5C 24 14 8B 7C 24 10 8B 74 24 18 85 DB 74 ? 80 3B 00 74 ? 85 F6 B8 ? ? ? ? 0F 44 F0 83 EC 0C 53 E8 ? ? ? ? C7 04 24 01 00")


// Steam API
SIGSCAN_DEFAULT(interfaceMgrSig, "89 0D ? ? ? ? 85 C9 0F", "")
Expand Down
1 change: 1 addition & 0 deletions src/SAR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ bool SAR::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerF
this->modules->AddModule<Matchmaking>(&matchmaking);
this->modules->AddModule<SteamAPI>(&steam);
this->modules->AddModule<VPhysics>(&vphysics);
this->modules->AddModule<VScript>(&vscript);
this->modules->InitAll();

SarInitHandler::RunAll();
Expand Down
Loading