2022-02-27 07:53:44 -05:00
|
|
|
#include <STDInclude.hpp>
|
2023-01-03 07:16:44 -05:00
|
|
|
|
|
|
|
#include <json.hpp>
|
|
|
|
|
2022-12-26 07:07:24 -05:00
|
|
|
#include "Theatre.hpp"
|
|
|
|
#include "UIFeeder.hpp"
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
namespace Components
|
|
|
|
{
|
|
|
|
Theatre::DemoInfo Theatre::CurrentInfo;
|
|
|
|
unsigned int Theatre::CurrentSelection;
|
|
|
|
std::vector<Theatre::DemoInfo> Theatre::Demos;
|
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
Dvar::Var Theatre::CLAutoRecord;
|
|
|
|
Dvar::Var Theatre::CLDemosKeep;
|
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
char Theatre::BaselineSnapshot[131072] = {0};
|
2017-01-19 16:23:59 -05:00
|
|
|
int Theatre::BaselineSnapshotMsgLen;
|
|
|
|
int Theatre::BaselineSnapshotMsgOff;
|
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
nlohmann::json Theatre::DemoInfo::to_json() const
|
|
|
|
{
|
|
|
|
return nlohmann::json
|
|
|
|
{
|
|
|
|
{ "mapname", mapname },
|
|
|
|
{ "gametype", gametype },
|
|
|
|
{ "author", author },
|
|
|
|
{ "length", length },
|
|
|
|
{ "timestamp", std::to_string(timeStamp) },
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-01-19 16:23:59 -05:00
|
|
|
void Theatre::GamestateWriteStub(Game::msg_t* msg, char byte)
|
|
|
|
{
|
|
|
|
Game::MSG_WriteLong(msg, 0);
|
|
|
|
Game::MSG_WriteByte(msg, byte);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Theatre::RecordGamestateStub()
|
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
const auto sequence = (*Game::serverMessageSequence - 1);
|
2022-06-26 15:36:23 -04:00
|
|
|
Game::FS_WriteToDemo(&sequence, 4, *Game::demoFile);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void Theatre::StoreBaseline(PBYTE snapshotMsg)
|
|
|
|
{
|
|
|
|
// Store offset and length
|
2022-12-05 13:45:14 -05:00
|
|
|
BaselineSnapshotMsgLen = *reinterpret_cast<int*>(snapshotMsg + 20);
|
|
|
|
BaselineSnapshotMsgOff = *reinterpret_cast<int*>(snapshotMsg + 28) - 7;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Copy to our snapshot buffer
|
2022-12-05 13:45:14 -05:00
|
|
|
std::memcpy(BaselineSnapshot, *reinterpret_cast<DWORD**>(snapshotMsg + 8), *reinterpret_cast<DWORD*>(snapshotMsg + 20));
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Theatre::BaselineStoreStub()
|
|
|
|
{
|
|
|
|
_asm
|
|
|
|
{
|
|
|
|
push edi
|
2022-12-05 13:45:14 -05:00
|
|
|
call StoreBaseline
|
2017-01-19 16:23:59 -05:00
|
|
|
pop edi
|
|
|
|
|
|
|
|
mov edx, 5ABEF5h
|
|
|
|
jmp edx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Theatre::WriteBaseline()
|
|
|
|
{
|
2022-08-13 11:19:45 -04:00
|
|
|
static unsigned char bufData[131072];
|
2022-12-05 13:45:14 -05:00
|
|
|
static unsigned char cmpData[131072];
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
Game::msg_t buf;
|
|
|
|
|
|
|
|
Game::MSG_Init(&buf, bufData, 131072);
|
2022-12-05 13:45:14 -05:00
|
|
|
Game::MSG_WriteData(&buf, &BaselineSnapshot[BaselineSnapshotMsgOff], BaselineSnapshotMsgLen - BaselineSnapshotMsgOff);
|
2017-01-19 16:23:59 -05:00
|
|
|
Game::MSG_WriteByte(&buf, 6);
|
|
|
|
|
2022-12-05 13:45:14 -05:00
|
|
|
const auto compressedSize = Game::MSG_WriteBitsCompress(false, buf.data, cmpData, buf.cursize);
|
2022-10-16 11:17:42 -04:00
|
|
|
const auto fileCompressedSize = compressedSize + 4;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
int byte8 = 8;
|
|
|
|
char byte0 = 0;
|
|
|
|
|
2022-06-26 15:36:23 -04:00
|
|
|
Game::FS_WriteToDemo(&byte0, 1, *Game::demoFile);
|
|
|
|
Game::FS_WriteToDemo(Game::serverMessageSequence, 4, *Game::demoFile);
|
|
|
|
Game::FS_WriteToDemo(&fileCompressedSize, 4, *Game::demoFile);
|
|
|
|
Game::FS_WriteToDemo(&byte8, 4, *Game::demoFile);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
for (auto i = 0; i < compressedSize; i += 1024)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
const auto size = std::min(compressedSize - i, 1024);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
if (i + size >= sizeof(cmpData))
|
|
|
|
{
|
2022-07-21 12:56:16 -04:00
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Writing compressed demo baseline exceeded buffer\n");
|
2017-01-19 16:23:59 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-06-26 15:36:23 -04:00
|
|
|
Game::FS_WriteToDemo(&cmpData[i], size, *Game::demoFile);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Theatre::BaselineToFileStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
2017-02-01 07:44:25 -05:00
|
|
|
pushad
|
2022-12-05 13:45:14 -05:00
|
|
|
call WriteBaseline
|
2017-02-01 07:44:25 -05:00
|
|
|
popad
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Restore overwritten operation
|
|
|
|
mov ecx, 0A5E9C4h
|
|
|
|
mov [ecx], 0
|
|
|
|
|
|
|
|
// Return to original code
|
2017-02-01 07:44:25 -05:00
|
|
|
push 5A863Ah
|
|
|
|
retn
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Theatre::AdjustTimeDeltaStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
mov eax, Game::demoPlaying
|
|
|
|
mov eax, [eax]
|
|
|
|
test al, al
|
|
|
|
jz continue
|
|
|
|
|
|
|
|
// delta doesn't drift for demos
|
|
|
|
retn
|
|
|
|
|
|
|
|
continue:
|
2017-02-01 07:44:25 -05:00
|
|
|
push 5A1AD0h
|
|
|
|
retn
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Theatre::ServerTimedOutStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
mov eax, Game::demoPlaying
|
|
|
|
mov eax, [eax]
|
|
|
|
test al, al
|
|
|
|
jz continue
|
|
|
|
|
|
|
|
mov eax, 5A8E70h
|
|
|
|
jmp eax
|
|
|
|
|
|
|
|
continue:
|
|
|
|
mov eax, 0B2BB90h
|
2017-02-01 07:44:25 -05:00
|
|
|
push 5A8E08h
|
|
|
|
retn
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__declspec(naked) void Theatre::UISetActiveMenuStub()
|
|
|
|
{
|
|
|
|
__asm
|
|
|
|
{
|
|
|
|
mov eax, Game::demoPlaying
|
|
|
|
mov eax, [eax]
|
|
|
|
test al, al
|
|
|
|
jz continue
|
|
|
|
|
|
|
|
mov eax, 4CB49Ch
|
|
|
|
jmp eax
|
|
|
|
|
|
|
|
continue:
|
|
|
|
mov ecx, [esp + 10h]
|
|
|
|
push 10h
|
|
|
|
push ecx
|
2017-02-01 07:44:25 -05:00
|
|
|
push 4CB3F6h
|
|
|
|
retn
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Theatre::RecordStub(int channel, char* message, char* file)
|
|
|
|
{
|
|
|
|
Game::Com_Printf(channel, message, file);
|
|
|
|
|
2022-12-05 13:45:14 -05:00
|
|
|
CurrentInfo.name = file;
|
|
|
|
CurrentInfo.mapname = (*Game::sv_mapname)->current.string;
|
|
|
|
CurrentInfo.gametype = (*Game::sv_gametype)->current.string;
|
|
|
|
CurrentInfo.author = Steam::SteamFriends()->GetPersonaName();
|
|
|
|
CurrentInfo.length = Game::Sys_Milliseconds();
|
|
|
|
std::time(&CurrentInfo.timeStamp);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void Theatre::StopRecordStub(int channel, char* message)
|
|
|
|
{
|
|
|
|
Game::Com_Printf(channel, message);
|
|
|
|
|
|
|
|
// Store correct length
|
2022-12-05 13:45:14 -05:00
|
|
|
CurrentInfo.length = Game::Sys_Milliseconds() - CurrentInfo.length;
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Write metadata
|
2022-12-05 13:45:14 -05:00
|
|
|
FileSystem::FileWriter meta(std::format("{}.json", CurrentInfo.name));
|
|
|
|
meta.write(nlohmann::json(CurrentInfo.to_json()).dump());
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2022-08-24 10:38:14 -04:00
|
|
|
void Theatre::LoadDemos([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
CurrentSelection = 0;
|
|
|
|
Demos.clear();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
const auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
for (auto demo : demos)
|
|
|
|
{
|
2022-12-11 12:54:24 -05:00
|
|
|
if (FileSystem::File meta = std::format("demos/{}.json", demo))
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
nlohmann::json metaObject;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
metaObject = nlohmann::json::parse(meta.getBuffer());
|
2022-12-05 13:45:14 -05:00
|
|
|
|
|
|
|
DemoInfo demoInfo;
|
|
|
|
demoInfo.name = demo.substr(0, demo.find_last_of("."));
|
|
|
|
demoInfo.author = metaObject["author"].get<std::string>();
|
|
|
|
demoInfo.gametype = metaObject["gametype"].get<std::string>();
|
|
|
|
demoInfo.mapname = metaObject["mapname"].get<std::string>();
|
|
|
|
demoInfo.length = metaObject["length"].get<int>();
|
|
|
|
auto timestamp = metaObject["timestamp"].get<std::string>();
|
2022-12-11 12:54:24 -05:00
|
|
|
demoInfo.timeStamp = std::strtoll(timestamp.data(), nullptr, 10);
|
2022-12-05 13:45:14 -05:00
|
|
|
|
|
|
|
Demos.push_back(demoInfo);
|
2022-10-16 11:17:42 -04:00
|
|
|
}
|
|
|
|
catch (const nlohmann::json::parse_error& ex)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reverse, latest demo first!
|
2022-12-05 13:45:14 -05:00
|
|
|
std::ranges::reverse(Demos);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2022-08-24 10:38:14 -04:00
|
|
|
void Theatre::DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
if (CurrentSelection < Demos.size())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
auto demoInfo = Demos.at(CurrentSelection);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-08-24 10:38:14 -04:00
|
|
|
Logger::Print("Deleting demo {}...\n", demoInfo.name);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-08-24 10:38:14 -04:00
|
|
|
FileSystem::_DeleteFile("demos", demoInfo.name + ".dm_13");
|
|
|
|
FileSystem::_DeleteFile("demos", demoInfo.name + ".dm_13.json");
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Reset our ui_demo_* dvars here, because the theater menu needs it.
|
|
|
|
Dvar::Var("ui_demo_mapname").set("");
|
|
|
|
Dvar::Var("ui_demo_mapname_localized").set("");
|
|
|
|
Dvar::Var("ui_demo_gametype").set("");
|
|
|
|
Dvar::Var("ui_demo_length").set("");
|
|
|
|
Dvar::Var("ui_demo_author").set("");
|
|
|
|
Dvar::Var("ui_demo_date").set("");
|
|
|
|
|
|
|
|
// Reload demos
|
2022-12-05 13:45:14 -05:00
|
|
|
LoadDemos(UIScript::Token(), info);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-24 10:38:14 -04:00
|
|
|
void Theatre::PlayDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
if (CurrentSelection < Demos.size())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
Command::Execute(std::format("demo {}", Demos[CurrentSelection].name), true);
|
2017-01-19 16:23:59 -05:00
|
|
|
Command::Execute("demoback", false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Theatre::GetDemoCount()
|
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
return Demos.size();
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Omit column here
|
|
|
|
const char* Theatre::GetDemoText(unsigned int item, int /*column*/)
|
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
if (item < Demos.size())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
auto info = Demos.at(item);
|
2017-01-19 16:23:59 -05:00
|
|
|
return Utils::String::VA("%s on %s", Game::UI_LocalizeGameType(info.gametype.data()), Game::UI_LocalizeMapName(info.mapname.data()));
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
void Theatre::SelectDemo(unsigned int index)
|
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
if (index < Demos.size())
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
CurrentSelection = index;
|
|
|
|
auto info = Demos.at(index);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
tm time;
|
2022-12-05 13:45:14 -05:00
|
|
|
char buffer[1000] = {0};
|
2017-01-19 16:23:59 -05:00
|
|
|
localtime_s(&time, &info.timeStamp);
|
|
|
|
asctime_s(buffer, sizeof buffer, &time);
|
|
|
|
|
|
|
|
Dvar::Var("ui_demo_mapname").set(info.mapname);
|
|
|
|
Dvar::Var("ui_demo_mapname_localized").set(Game::UI_LocalizeMapName(info.mapname.data()));
|
|
|
|
Dvar::Var("ui_demo_gametype").set(Game::UI_LocalizeGameType(info.gametype.data()));
|
|
|
|
Dvar::Var("ui_demo_length").set(Utils::String::FormatTimeSpan(info.length));
|
|
|
|
Dvar::Var("ui_demo_author").set(info.author);
|
|
|
|
Dvar::Var("ui_demo_date").set(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
int Theatre::CL_FirstSnapshot_Stub()
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2023-01-03 15:17:46 -05:00
|
|
|
if (CLAutoRecord.get<bool>() && !*Game::demoPlaying)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
std::vector<std::string> files;
|
2022-12-05 13:45:14 -05:00
|
|
|
auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
for (auto& demo : demos)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
if (Utils::String::StartsWith(demo, "auto_"))
|
|
|
|
{
|
|
|
|
files.push_back(demo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
auto numDel = static_cast<int>(files.size()) - CLDemosKeep.get<int>();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-12-05 13:45:14 -05:00
|
|
|
for (auto i = 0; i < numDel; ++i)
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-06-12 17:07:53 -04:00
|
|
|
Logger::Print("Deleting old demo {}\n", files[i]);
|
2022-12-11 12:54:24 -05:00
|
|
|
FileSystem::_DeleteFile("demos", files[i]);
|
|
|
|
FileSystem::_DeleteFile("demos", std::format("%s.json", files[i]));
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2022-12-11 12:54:24 -05:00
|
|
|
Command::Execute(Utils::String::VA("record auto_%lld", std::time(nullptr)), true);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
return Utils::Hook::Call<int()>(0x42BBB0)(); // DB_GetLoadedFlags
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2023-01-03 15:17:46 -05:00
|
|
|
void Theatre::SV_SpawnServer_Stub()
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
2022-12-05 13:45:14 -05:00
|
|
|
StopRecording();
|
2023-01-03 15:17:46 -05:00
|
|
|
Utils::Hook::Call<void()>(0x464A60)(); // Com_SyncThreads
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
|
2017-04-07 15:37:01 -04:00
|
|
|
void Theatre::StopRecording()
|
2017-01-19 16:23:59 -05:00
|
|
|
{
|
|
|
|
if (*Game::demoRecording)
|
|
|
|
{
|
|
|
|
Command::Execute("stoprecord", true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Theatre::Theatre()
|
|
|
|
{
|
2023-01-03 15:17:46 -05:00
|
|
|
CLAutoRecord = Dvar::Register<bool>("cl_autoRecord", true, Game::DVAR_ARCHIVE, "Automatically record games");
|
|
|
|
CLDemosKeep = Dvar::Register<int>("cl_demosKeep", 30, 1, 999, Game::DVAR_ARCHIVE, "How many demos to keep with autorecord");
|
2017-01-19 16:23:59 -05:00
|
|
|
|
2022-12-05 13:45:14 -05:00
|
|
|
Utils::Hook(0x5A8370, GamestateWriteStub, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x5A85D2, RecordGamestateStub, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x5ABE36, BaselineStoreStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook(0x5A8630, BaselineToFileStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook(0x4CB3EF, UISetActiveMenuStub, HOOK_JUMP).install()->quick();
|
|
|
|
Utils::Hook(0x50320E, AdjustTimeDeltaStub, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x5A8E03, ServerTimedOutStub, HOOK_JUMP).install()->quick();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Hook commands to enforce metadata generation
|
2022-12-05 13:45:14 -05:00
|
|
|
Utils::Hook(0x5A82AE, RecordStub, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x5A8156, StopRecordStub, HOOK_CALL).install()->quick();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Autorecording
|
2023-01-03 15:17:46 -05:00
|
|
|
Utils::Hook(0x5A1D6A, CL_FirstSnapshot_Stub, HOOK_CALL).install()->quick();
|
|
|
|
Utils::Hook(0x4A712A, SV_SpawnServer_Stub, HOOK_CALL).install()->quick();
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// UIScripts
|
2022-12-05 13:45:14 -05:00
|
|
|
UIScript::Add("loadDemos", LoadDemos);
|
|
|
|
UIScript::Add("launchDemo", PlayDemo);
|
|
|
|
UIScript::Add("deleteDemo", DeleteDemo);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Feeder
|
2022-12-05 13:45:14 -05:00
|
|
|
UIFeeder::Add(10.0f, GetDemoCount, GetDemoText, SelectDemo);
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps
|
2020-12-09 14:13:34 -05:00
|
|
|
if (!Dedicated::IsEnabled()) Utils::Hook::Set<const char*>(0x47440B, "mp/defaultStringTable.csv");
|
2017-01-19 16:23:59 -05:00
|
|
|
|
|
|
|
// Change font size
|
2023-01-03 15:17:46 -05:00
|
|
|
Utils::Hook::Set<std::uint8_t>(0x5AC854, 2);
|
|
|
|
Utils::Hook::Set<std::uint8_t>(0x5AC85A, 2);
|
2017-01-19 16:23:59 -05:00
|
|
|
}
|
|
|
|
}
|