diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index cb9257ea..75f6d6c2 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -85,8 +85,6 @@ namespace Components std::string rotation = Dvar::Var("sv_mapRotationCurrent").Get(); - // Ignores " for now, too lazy to implement - // TODO: Implement! auto tokens = Utils::Explode(rotation, ' '); for (unsigned int i = 0; i < (tokens.size() - 1); i += 2) @@ -122,11 +120,6 @@ namespace Components Logger::Print("Applying new gametype: %s\n", value.data()); Dvar::Var("g_gametype").Set(value); } - else if (key == "fs_game") - { - Logger::Print("Applying new mod: %s\n", value.data()); - Dvar::Var("fs_game").Set(value); - } else { Logger::Print("Unsupported maprotation key '%s', motherfucker!\n", key.data()); diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index 49bcdd09..2e960baa 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -23,6 +23,57 @@ namespace Components } } + void FileSystem::FileWriter::Write(std::string data) + { + if (this->Handle) + { + Game::FS_Write(data.data(), data.size(), this->Handle); + } + } + + void FileSystem::FileWriter::Open() + { + this->Handle = Game::FS_FOpenFileWrite(this->FilePath.data()); + } + + void FileSystem::FileWriter::Close() + { + if (this->Handle) + { + Game::FS_FCloseFile(this->Handle); + } + } + + std::vector FileSystem::GetFileList(std::string path, std::string extension) + { + std::vector fileList; + + int numFiles = 0; + char** files = Game::FS_ListFiles(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 0); + + if (files) + { + for (int i = 0; i < numFiles; i++) + { + if (files[i]) + { + fileList.push_back(files[i]); + } + } + + Game::FS_FreeFileList(files); + } + + return fileList; + } + + void FileSystem::DeleteFile(std::string folder, std::string file) + { + char path[MAX_PATH] = { 0 }; + Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").Get(), (char*)0x63D0BB8, Utils::VA("%s/%s", folder.data(), file.data()), (char**)&path); + Game::FS_Remove(path); + } + int FileSystem::ExecIsFSStub(const char* execFilename) { return !File(execFilename).Exists(); diff --git a/src/Components/Modules/FileSystem.hpp b/src/Components/Modules/FileSystem.hpp index 4bc3307c..4e0313ab 100644 --- a/src/Components/Modules/FileSystem.hpp +++ b/src/Components/Modules/FileSystem.hpp @@ -21,9 +21,28 @@ namespace Components void Read(); }; + class FileWriter + { + public: + FileWriter(std::string file) : FilePath(file), Handle(0) { this->Open(); }; + ~FileWriter() { this->Close(); }; + + void Write(std::string data); + + private: + int Handle; + std::string FilePath; + + void Open(); + void Close(); + }; + FileSystem(); const char* GetName() { return "FileSystem"; }; + static std::vector GetFileList(std::string path, std::string extension); + static void DeleteFile(std::string folder, std::string file); + private: static int ExecIsFSStub(const char* execFilename); }; diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index f951c9f2..48293fff 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -4,14 +4,14 @@ namespace Components { ServerInfo::Container ServerInfo::PlayerContainer; - int ServerInfo::GetPlayerCount() + unsigned int ServerInfo::GetPlayerCount() { return ServerInfo::PlayerContainer.PlayerList.size(); } - const char* ServerInfo::GetPlayerText(int index, int column) + const char* ServerInfo::GetPlayerText(unsigned int index, int column) { - if ((unsigned int)index < ServerInfo::PlayerContainer.PlayerList.size()) + if (index < ServerInfo::PlayerContainer.PlayerList.size()) { switch (column) { @@ -32,7 +32,7 @@ namespace Components return ""; } - void ServerInfo::SelectPlayer(int index) + void ServerInfo::SelectPlayer(unsigned int index) { ServerInfo::PlayerContainer.CurrentPlayer = index; } diff --git a/src/Components/Modules/ServerInfo.hpp b/src/Components/Modules/ServerInfo.hpp index 8aa13828..b53a90ea 100644 --- a/src/Components/Modules/ServerInfo.hpp +++ b/src/Components/Modules/ServerInfo.hpp @@ -17,7 +17,7 @@ namespace Components std::string Name; }; - int CurrentPlayer; + unsigned int CurrentPlayer; std::vector PlayerList; Network::Address Target; }; @@ -26,8 +26,8 @@ namespace Components static void ServerStatus(); - static int GetPlayerCount(); - static const char* GetPlayerText(int index, int column); - static void SelectPlayer(int index); + static unsigned int GetPlayerCount(); + static const char* GetPlayerText(unsigned int index, int column); + static void SelectPlayer(unsigned int index); }; } diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 855ece83..4840a0fe 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -12,7 +12,7 @@ namespace Components std::vector ServerList::OfflineList; std::vector ServerList::FavouriteList; - std::vector ServerList::VisibleList; + std::vector ServerList::VisibleList; std::vector* ServerList::GetList() { @@ -49,12 +49,12 @@ namespace Components return (Dvar::Var("ui_netSource").Get() == 1); } - int ServerList::GetServerCount() + unsigned int ServerList::GetServerCount() { - return (int)ServerList::VisibleList.size(); + return ServerList::VisibleList.size(); } - const char* ServerList::GetServerText(int index, int column) + const char* ServerList::GetServerText(unsigned int index, int column) { ServerList::ServerInfo* info = ServerList::GetServer(index); @@ -121,9 +121,9 @@ namespace Components return ""; } - void ServerList::SelectServer(int index) + void ServerList::SelectServer(unsigned int index) { - ServerList::CurrentServer = (unsigned int)index; + ServerList::CurrentServer = index; ServerList::ServerInfo* info = ServerList::GetCurrentServer(); @@ -412,8 +412,8 @@ namespace Components { qsort(ServerList::VisibleList.data(), ServerList::VisibleList.size(), sizeof(int), [] (const void* first, const void* second) { - int server1 = *(int*)first; - int server2 = *(int*)second; + unsigned int server1 = *(unsigned int*)first; + unsigned int server2 = *(unsigned int*)second; ServerInfo* info1 = nullptr; ServerInfo* info2 = nullptr; @@ -421,8 +421,8 @@ namespace Components auto list = ServerList::GetList(); if (!list) return 0; - if (list->size() > (unsigned int)server1) info1 = &(*list)[server1]; - if (list->size() > (unsigned int)server2) info2 = &(*list)[server2]; + if (list->size() > server1) info1 = &(*list)[server1]; + if (list->size() > server2) info2 = &(*list)[server2]; if (!info1) return 1; if (!info2) return -1; @@ -445,14 +445,14 @@ namespace Components }); } - ServerList::ServerInfo* ServerList::GetServer(int index) + ServerList::ServerInfo* ServerList::GetServer(unsigned int index) { - if (ServerList::VisibleList.size() > (unsigned int)index) + if (ServerList::VisibleList.size() > index) { auto list = ServerList::GetList(); if (!list) return nullptr; - if (list->size() > (unsigned int)ServerList::VisibleList[index]) + if (list->size() > ServerList::VisibleList[index]) { return &(*list)[ServerList::VisibleList[index]]; } diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 9d84bf39..d7c06805 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -93,10 +93,10 @@ namespace Components std::mutex Mutex; }; - static int GetServerCount(); - static const char* GetServerText(int index, int column); + static unsigned int GetServerCount(); + static const char* GetServerText(unsigned int index, int column); static const char* GetServerText(ServerInfo* server, int column); - static void SelectServer(int index); + static void SelectServer(unsigned int index); static void UpdateSource(); static void UpdateGameType(); @@ -108,7 +108,7 @@ namespace Components static void LoadFavourties(); static void StoreFavourite(std::string server); - static ServerInfo* GetServer(int index); + static ServerInfo* GetServer(unsigned int index); static std::vector* GetList(); static int SortKey; @@ -121,6 +121,6 @@ namespace Components static std::vector OfflineList; static std::vector FavouriteList; - static std::vector VisibleList; + static std::vector VisibleList; }; } diff --git a/src/Components/Modules/Theatre.cpp b/src/Components/Modules/Theatre.cpp index 37818696..73605a46 100644 --- a/src/Components/Modules/Theatre.cpp +++ b/src/Components/Modules/Theatre.cpp @@ -2,6 +2,8 @@ namespace Components { + Theatre::Container Theatre::DemoContainer; + char Theatre::BaselineSnapshot[131072] = { 0 }; PBYTE Theatre::BaselineSnapshotMsg = 0; int Theatre::BaselineSnapshotMsgLen; @@ -129,17 +131,17 @@ namespace Components void __declspec(naked) Theatre::UISetActiveMenuStub() { - if (*Game::demoPlaying == 1) - { - __asm - { - mov eax, 4CB49Ch - jmp eax - } - } - __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 @@ -148,6 +150,125 @@ namespace Components } } + void Theatre::RecordStub(int channel, char* message, char* file) + { + Game::Com_Printf(channel, message, file); + + Theatre::DemoContainer.CurrentInfo.Name = file; + Theatre::DemoContainer.CurrentInfo.Mapname = Dvar::Var("mapname").Get(); + Theatre::DemoContainer.CurrentInfo.Gametype = Dvar::Var("g_gametype").Get(); + Theatre::DemoContainer.CurrentInfo.Author = Steam::SteamFriends()->GetPersonaName(); + Theatre::DemoContainer.CurrentInfo.Length = Game::Com_Milliseconds(); + std::time(&Theatre::DemoContainer.CurrentInfo.TimeStamp); + } + + void Theatre::StopRecordStub(int channel, char* message) + { + Game::Com_Printf(channel, message); + + // Store correct length + Theatre::DemoContainer.CurrentInfo.Length = Game::Com_Milliseconds() - Theatre::DemoContainer.CurrentInfo.Length; + + // Write metadata + FileSystem::FileWriter meta(Utils::VA("%s.json", Theatre::DemoContainer.CurrentInfo.Name.data())); + meta.Write(json11::Json(Theatre::DemoContainer.CurrentInfo).dump()); + } + + void Theatre::LoadDemos() + { + Theatre::DemoContainer.CurrentSelection = 0; + Theatre::DemoContainer.Demos.clear(); + + auto demos = FileSystem::GetFileList("demos/", "dm_13"); + + for (auto demo : demos) + { + FileSystem::File meta(Utils::VA("demos/%s.json", demo.data())); + + if (meta.Exists()) + { + std::string error; + json11::Json metaObject = json11::Json::parse(meta.GetBuffer(), error); + + if (metaObject.is_object()) + { + Theatre::Container::DemoInfo info; + + info.Name = demo.substr(0, demo.find_last_of(".")); + info.Author = metaObject["author"].string_value(); + info.Gametype = metaObject["gametype"].string_value(); + info.Mapname = metaObject["mapname"].string_value(); + info.Length = (int)metaObject["length"].number_value(); + info.TimeStamp = _atoi64(metaObject["timestamp"].string_value().data()); + + Theatre::DemoContainer.Demos.push_back(info); + } + } + } + + // Reverse, latest demo first! + std::reverse(Theatre::DemoContainer.Demos.begin(), Theatre::DemoContainer.Demos.end()); + } + + void Theatre::DeleteDemo() + { + if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size()) + { + Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection]; + + Logger::Print("Deleting demo %s...\n", info.Name.data()); + + FileSystem::DeleteFile("demos", info.Name + ".dm_13"); + FileSystem::DeleteFile("demos", info.Name + ".dm_13.json"); + + // Reload demos + Theatre::LoadDemos(); + } + } + + void Theatre::PlayDemo() + { + if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size()) + { + Command::Execute(Utils::VA("demo %s", Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection].Name.data()), true); + Command::Execute("demoback", false); + } + } + + unsigned int Theatre::GetDemoCount() + { + return Theatre::DemoContainer.Demos.size(); + } + + // Omit column here + const char* Theatre::GetDemoText(unsigned int item, int column) + { + if (item < Theatre::DemoContainer.Demos.size()) + { + Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[item]; + + return Utils::VA("%s on %s", Game::UI_LocalizeGameType(info.Gametype.data()), Game::UI_LocalizeMapName(info.Mapname.data())); + } + + return ""; + } + + void Theatre::SelectDemo(unsigned int index) + { + if (index < Theatre::DemoContainer.Demos.size()) + { + Theatre::DemoContainer.CurrentSelection = index; + Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[index]; + + 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(info.Length); // TODO: Parse as readable string + Dvar::Var("ui_demo_author").Set(info.Author); + Dvar::Var("ui_demo_date").Set(std::asctime(std::localtime(&info.TimeStamp))); + } + } + Theatre::Theatre() { Utils::Hook(0x5A8370, Theatre::GamestateWriteStub, HOOK_CALL).Install()->Quick(); @@ -158,10 +279,23 @@ namespace Components Utils::Hook(0x50320E, Theatre::AdjustTimeDeltaStub, HOOK_CALL).Install()->Quick(); Utils::Hook(0x5A8E03, Theatre::ServerTimedOutStub, HOOK_JUMP).Install()->Quick(); + // Hook commands to enforce metadata generation + Utils::Hook(0x5A82AE, Theatre::RecordStub, HOOK_CALL).Install()->Quick(); + Utils::Hook(0x5A8156, Theatre::StopRecordStub, HOOK_CALL).Install()->Quick(); + + // UIScripts + UIScript::Add("loadDemos", Theatre::LoadDemos); + UIScript::Add("launchDemo", Theatre::PlayDemo); + UIScript::Add("deleteDemo", Theatre::DeleteDemo); + + // Feeder + UIFeeder::Add(10.0f, Theatre::GetDemoCount, Theatre::GetDemoText, Theatre::SelectDemo); + // set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps if(!Dedicated::IsDedicated()) Utils::Hook::Set(0x47440B, "mp/defaultStringTable.csv"); - *(BYTE*)0x5AC854 = 4; - *(BYTE*)0x5AC85A = 4; + // Change font size + Utils::Hook::Set(0x5AC854, 2); + Utils::Hook::Set(0x5AC85A, 2); } } diff --git a/src/Components/Modules/Theatre.hpp b/src/Components/Modules/Theatre.hpp index 31756696..c0289f4f 100644 --- a/src/Components/Modules/Theatre.hpp +++ b/src/Components/Modules/Theatre.hpp @@ -7,6 +7,37 @@ namespace Components const char* GetName() { return "Theatre"; }; private: + struct Container + { + struct DemoInfo + { + std::string Name; + std::string Mapname; + std::string Gametype; + std::string Author; + int Length; + std::time_t TimeStamp; + + json11::Json to_json() const + { + return json11::Json::object + { + { "mapname", Mapname }, + { "gametype", Gametype }, + { "author", Author }, + { "length", Length }, + { "timestamp", Utils::VA("%lld", TimeStamp) } //Ugly, but prevents information loss + }; + } + }; + + DemoInfo CurrentInfo; + unsigned int CurrentSelection; + std::vector Demos; + }; + + static Container DemoContainer; + static char BaselineSnapshot[131072]; static PBYTE BaselineSnapshotMsg; static int BaselineSnapshotMsgLen; @@ -14,6 +45,14 @@ namespace Components static void WriteBaseline(); + static void LoadDemos(); + static void DeleteDemo(); + static void PlayDemo(); + + static unsigned int GetDemoCount(); + static const char* GetDemoText(unsigned int item, int column); + static void SelectDemo(unsigned int index); + static void GamestateWriteStub(Game::msg_t* msg, char byte); static void RecordGamestateStub(); static void BaselineStoreStub(); @@ -21,5 +60,8 @@ namespace Components static void AdjustTimeDeltaStub(); static void ServerTimedOutStub(); static void UISetActiveMenuStub(); + + static void RecordStub(int channel, char* message, char* file); + static void StopRecordStub(int channel, char* message); }; } diff --git a/src/Components/Modules/UIFeeder.cpp b/src/Components/Modules/UIFeeder.cpp index c89d1487..6bbb500d 100644 --- a/src/Components/Modules/UIFeeder.cpp +++ b/src/Components/Modules/UIFeeder.cpp @@ -20,7 +20,7 @@ namespace Components return nullptr; } - int UIFeeder::GetItemCount() + unsigned int UIFeeder::GetItemCount() { if (UIFeeder::Feeders.find(UIFeeder::Current.Feeder) != UIFeeder::Feeders.end()) { @@ -65,7 +65,7 @@ namespace Components retn continue: - fld ds : 739FD0h + fld ds:739FD0h mov eax, 4C25D6h jmp eax diff --git a/src/Components/Modules/UIFeeder.hpp b/src/Components/Modules/UIFeeder.hpp index dc6cd950..41699a79 100644 --- a/src/Components/Modules/UIFeeder.hpp +++ b/src/Components/Modules/UIFeeder.hpp @@ -3,9 +3,9 @@ namespace Components class UIFeeder : public Component { public: - typedef int(__cdecl * GetItemCount_t)(); - typedef const char* (__cdecl * GetItemText_t)(int index, int column); - typedef void(__cdecl * Select_t)(int index); + typedef unsigned int(__cdecl * GetItemCount_t)(); + typedef const char* (__cdecl * GetItemText_t)(unsigned int index, int column); + typedef void(__cdecl * Select_t)(unsigned int index); struct Callbacks { @@ -31,7 +31,7 @@ namespace Components static Container Current; static void GetItemCountStub(); - static int GetItemCount(); + static unsigned int GetItemCount(); static void GetItemTextStub(); static const char* GetItemText(); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 3fed37a1..4cbaa28d 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -102,7 +102,7 @@ namespace Game typedef void(__cdecl * FS_FreeFileList_t)(char** list); extern FS_FreeFileList_t FS_FreeFileList; - typedef int(__cdecl * FS_FOpenFileAppend_t)(char* file); + typedef int(__cdecl * FS_FOpenFileAppend_t)(const char* file); extern FS_FOpenFileAppend_t FS_FOpenFileAppend; extern FS_FOpenFileAppend_t FS_FOpenFileWrite; @@ -118,7 +118,7 @@ namespace Game typedef bool(__cdecl * FS_WriteFile_t)(char* filename, char* folder, void* buffer, int size); extern FS_WriteFile_t FS_WriteFile; - typedef int(__cdecl * FS_Write_t)(void* buffer, size_t size, int file); + typedef int(__cdecl * FS_Write_t)(const void* buffer, size_t size, int file); extern FS_Write_t FS_Write; typedef int(__cdecl * FS_Read_t)(void* buffer, size_t size, int file);