From 7c4048690b8d9779414cb8f60bd6f4efe19acbfa Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Wed, 9 Mar 2016 23:04:28 +0100 Subject: [PATCH] Fix OnStartRecording and OnStopRecording forwards on linux CHLTVServer::StartRecording is called directly in tv_record on linux, ignoring the vtable. Add a detour on linux for these two functions, so we always notice when recording starts. Windows actually always uses the vtable to get the function address, so we don't need to detour anything on windows. --- AMBuilder | 6 ++ extension.cpp | 4 ++ extension.h | 3 + forwards.cpp | 138 ++++++++++++++++++++++++++++++++------ forwards.h | 18 +++++ hltvserverwrapper.cpp | 11 +++ sourcetvmanager.games.txt | 28 ++++++++ 7 files changed, 189 insertions(+), 19 deletions(-) diff --git a/AMBuilder b/AMBuilder index 1a45f1d..f9ce5ca 100644 --- a/AMBuilder +++ b/AMBuilder @@ -36,6 +36,12 @@ for sdk_name in ['css', 'csgo']: binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk) compiler = binary.compiler + if builder.target_platform == 'linux': + binary.sources += [ + os.path.join(Extension.sm_root, 'public', 'CDetour', 'CDetour.cpp'), + os.path.join(Extension.sm_root, 'public', 'asm', 'asm.c') + ] + if sdk.name == 'csgo': compiler.cxxincludes += [ os.path.join(sdk.path, 'common', 'protobuf-2.5.0', 'src'), diff --git a/extension.cpp b/extension.cpp index b3f4199..ea861c3 100644 --- a/extension.cpp +++ b/extension.cpp @@ -95,6 +95,10 @@ bool SourceTVManager::SDK_OnLoad(char *error, size_t maxlength, bool late) g_HLTVServers.InitHooks(); +#ifndef WIN32 + CDetourManager::Init(smutils->GetScriptingEngine(), g_pGameConf); +#endif + sharesys->AddNatives(myself, sourcetv_natives); sharesys->RegisterLibrary(myself, "sourcetvmanager"); diff --git a/extension.h b/extension.h index a650c65..45292ec 100644 --- a/extension.h +++ b/extension.h @@ -40,6 +40,9 @@ #include "smsdk_ext.h" #include #include +#ifndef WIN32 +#include "CDetour\detours.h" +#endif #include "ihltvdirector.h" #include "ihltv.h" #include "iserver.h" diff --git a/forwards.cpp b/forwards.cpp index 2854bce..aa0a557 100644 --- a/forwards.cpp +++ b/forwards.cpp @@ -35,12 +35,15 @@ CForwardManager g_pSTVForwards; +// Only windows always uses the vtable for these. Linux does direct calls, so we use detours there. +#ifdef WIN32 SH_DECL_HOOK2_void(IDemoRecorder, StartRecording, SH_NOATTRIB, 0, const char *, bool) #if SOURCE_ENGINE == SE_CSGO SH_DECL_HOOK1_void(IDemoRecorder, StopRecording, SH_NOATTRIB, 0, CGameInfo const *) #else SH_DECL_HOOK0_void(IDemoRecorder, StopRecording, SH_NOATTRIB, 0) -#endif +#endif // SOURCE_ENGINE == SE_CSGO +#endif // !WIN32 #if SOURCE_ENGINE == SE_CSGO SH_DECL_MANUALHOOK13(CHLTVServer_ConnectClient, 0, 0, 0, IClient *, const netadr_t &, int, int, int, const char *, const char *, const char *, int, CUtlVector &, bool, CrossPlayPlatform_t, const unsigned char *, int); @@ -126,14 +129,18 @@ void CForwardManager::Shutdown() void CForwardManager::HookRecorder(IDemoRecorder *recorder) { +#ifdef WIN32 SH_ADD_HOOK(IDemoRecorder, StartRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStartRecording_Post), true); SH_ADD_HOOK(IDemoRecorder, StopRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStopRecording_Post), true); +#endif } void CForwardManager::UnhookRecorder(IDemoRecorder *recorder) { +#ifdef WIN32 SH_REMOVE_HOOK(IDemoRecorder, StartRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStartRecording_Post), true); SH_REMOVE_HOOK(IDemoRecorder, StopRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStopRecording_Post), true); +#endif } void CForwardManager::HookServer(HLTVServerWrapper *wrapper) @@ -367,21 +374,12 @@ void CForwardManager::OnSpectatorPutInServer() RETURN_META(MRES_IGNORED); } + +// These two hooks are actually only hooked on windows. void CForwardManager::OnStartRecording_Post(const char *filename, bool bContinuously) { - if (m_StartRecordingFwd->GetFunctionCount() == 0) - RETURN_META(MRES_IGNORED); - IDemoRecorder *recorder = META_IFACEPTR(IDemoRecorder); - HLTVServerWrapper *wrapper = g_HLTVServers.GetWrapper(recorder); - int instance = -1; - if (wrapper) - instance = wrapper->GetInstanceNumber(); - - m_StartRecordingFwd->PushCell(instance); - m_StartRecordingFwd->PushString(filename); - m_StartRecordingFwd->Execute(); - + CallOnStartRecording(recorder, filename, bContinuously); RETURN_META(MRES_IGNORED); } @@ -391,12 +389,33 @@ void CForwardManager::OnStopRecording_Post(CGameInfo const *info) void CForwardManager::OnStopRecording_Post() #endif { - if (m_StopRecordingFwd->GetFunctionCount() == 0) - RETURN_META(MRES_IGNORED); - IDemoRecorder *recorder = META_IFACEPTR(IDemoRecorder); + CallOnStopRecording(recorder); + RETURN_META(MRES_IGNORED); +} + +void CForwardManager::CallOnStartRecording(IDemoRecorder *recorder, const char *filename, bool bContinuously) +{ + if (m_StartRecordingFwd->GetFunctionCount() == 0) + return; + + HLTVServerWrapper *wrapper = g_HLTVServers.GetWrapper(recorder); + int instance = -1; + if (wrapper) + instance = wrapper->GetInstanceNumber(); + + m_StartRecordingFwd->PushCell(instance); + m_StartRecordingFwd->PushString(filename); + m_StartRecordingFwd->Execute(); +} + +void CForwardManager::CallOnStopRecording(IDemoRecorder *recorder) +{ + if (m_StopRecordingFwd->GetFunctionCount() == 0) + return; + if (!recorder->IsRecording()) - RETURN_META(MRES_IGNORED); + return; char *pDemoFile = (char *)recorder->GetDemoFile(); @@ -409,6 +428,87 @@ void CForwardManager::OnStopRecording_Post() m_StopRecordingFwd->PushString(pDemoFile); m_StopRecordingFwd->PushCell(recorder->GetRecordingTick()); m_StopRecordingFwd->Execute(); +} - RETURN_META(MRES_IGNORED); -} \ No newline at end of file +// Only need to detour these on Linux. Windows always uses the vtable. +#ifndef WIN32 +DETOUR_DECL_MEMBER2(DetourHLTVStartRecording, void, const char *, filename, bool, bContinuously) +{ + // Call the original first. + DETOUR_MEMBER_CALL(DetourHLTVStartRecording)(filename, bContinuously); + + IDemoRecorder *recorder = (IDemoRecorder *)this; + g_pSTVForwards.CallOnStartRecording(recorder, filename, bContinuously); +} + +#if SOURCE_ENGINE == SE_CSGO +DETOUR_DECL_MEMBER1(DetourHLTVStopRecording, void, CGameInfo const *, info) +#else +DETOUR_DECL_MEMBER0(DetourHLTVStopRecording, void) +#endif +{ + // Call the original first. +#if SOURCE_ENGINE == SE_CSGO + DETOUR_MEMBER_CALL(DetourHLTVStopRecording)(info); +#else + DETOUR_MEMBER_CALL(DetourHLTVStopRecording)(); +#endif + + IDemoRecorder *recorder = (IDemoRecorder *)this; + g_pSTVForwards.CallOnStopRecording(recorder); +} + +bool CForwardManager::CreateStartRecordingDetour() +{ + if (m_bStartRecordingDetoured) + return true; + + m_DStartRecording = DETOUR_CREATE_MEMBER(DetourHLTVStartRecording, "CHLTVDemoRecorder::StartRecording"); + + if (m_DStartRecording != nullptr) + { + m_DStartRecording->EnableDetour(); + m_bStartRecordingDetoured = true; + return true; + } + smutils->LogError(myself, "CHLTVDemoRecorder::StartRecording detour could not be initialized."); + return false; +} + +void CForwardManager::RemoveStartRecordingDetour() +{ + if (m_DStartRecording != nullptr) + { + m_DStartRecording->Destroy(); + m_DStartRecording = nullptr; + } + m_bStartRecordingDetoured = false; +} + +bool CForwardManager::CreateStopRecordingDetour() +{ + if (m_bStopRecordingDetoured) + return true; + + m_DStopRecording = DETOUR_CREATE_MEMBER(DetourHLTVStopRecording, "CHLTVDemoRecorder::StopRecording"); + + if (m_DStopRecording != nullptr) + { + m_DStopRecording->EnableDetour(); + m_bStopRecordingDetoured = true; + return true; + } + smutils->LogError(myself, "CHLTVDemoRecorder::StartRecording detour could not be initialized."); + return false; +} + +void CForwardManager::RemoveStopRecordingDetour() +{ + if (m_DStopRecording != nullptr) + { + m_DStopRecording->Destroy(); + m_DStopRecording = nullptr; + } + m_bStopRecordingDetoured = false; +} +#endif \ No newline at end of file diff --git a/forwards.h b/forwards.h index 8829af1..5e7c1e4 100644 --- a/forwards.h +++ b/forwards.h @@ -69,9 +69,19 @@ public: void HookServer(HLTVServerWrapper *server); void UnhookServer(HLTVServerWrapper *server); +#ifndef WIN32 + bool CreateStartRecordingDetour(); + void RemoveStartRecordingDetour(); + bool CreateStopRecordingDetour(); + void RemoveStopRecordingDetour(); +#endif + void CallOnServerStart(IHLTVServer *server); void CallOnServerShutdown(IHLTVServer *server); + void CallOnStartRecording(IDemoRecorder *recorder, const char *filename, bool bContinuously); + void CallOnStopRecording(IDemoRecorder *recorder); + private: void HookClient(IClient *client); void UnhookClient(IClient *client); @@ -106,6 +116,14 @@ private: bool m_bHasRejectConnectionOffset = false; bool m_bHasGetChallengeTypeOffset = false; bool m_bHasActivatePlayerOffset = false; + + // Only need the detours on linux. Windows always uses its vtables.. +#ifndef WIN32 + bool m_bStartRecordingDetoured = false; + CDetour *m_DStartRecording = nullptr; + bool m_bStopRecordingDetoured = false; + CDetour *m_DStopRecording = nullptr; +#endif }; extern CForwardManager g_pSTVForwards; diff --git a/hltvserverwrapper.cpp b/hltvserverwrapper.cpp index 532d21a..d4a60e4 100644 --- a/hltvserverwrapper.cpp +++ b/hltvserverwrapper.cpp @@ -261,6 +261,11 @@ void HLTVServerWrapperManager::InitHooks() void HLTVServerWrapperManager::ShutdownHooks() { +#ifndef WIN32 + g_pSTVForwards.RemoveStartRecordingDetour(); + g_pSTVForwards.RemoveStopRecordingDetour(); +#endif + #if SOURCE_ENGINE != SE_CSGO if (m_bSendNetMsgHooked) { @@ -272,6 +277,12 @@ void HLTVServerWrapperManager::ShutdownHooks() void HLTVServerWrapperManager::AddServer(IHLTVServer *hltvserver) { +#ifndef WIN32 + // Create the detours once the first sourcetv server is created. + g_pSTVForwards.CreateStartRecordingDetour(); + g_pSTVForwards.CreateStopRecordingDetour(); +#endif + HLTVServerWrapper *wrapper = new HLTVServerWrapper(hltvserver); m_HLTVServers.append(wrapper); } diff --git a/sourcetvmanager.games.txt b/sourcetvmanager.games.txt index b803011..ae064d2 100644 --- a/sourcetvmanager.games.txt +++ b/sourcetvmanager.games.txt @@ -111,6 +111,20 @@ "linux" "@_ZN11CHLTVServer24GetRecordingDemoFilenameEv" "windows" "\x81\xC1\x2A\x2A\x2A\x2A\x8B\x01\xFF\x20" } + + // StartRecording and StopRecording are virtual, but get called directly in the linux binary.. + // Need to add a detour. + "CHLTVDemoRecorder::StartRecording" + { + "library" "engine" + "linux" "@_ZN17CHLTVDemoRecorder14StartRecordingEPKcb" + } + + "CHLTVDemoRecorder::StopRecording" + { + "library" "engine" + "linux" "@_ZN17CHLTVDemoRecorder13StopRecordingEPK9CGameInfo" + } } } "cstrike" @@ -237,6 +251,20 @@ // "HLTV server shutting down" "windows" "\x56\x8B\xF1\x8B\x86\x2A\x2A\x2A\x2A\x8D\x8E\x2A\x2A\x2A\x2A\xFF\x50\x2A\x8B\x86\x2A\x2A\x2A\x2A\x8D\x8E" } + + // StartRecording and StopRecording are virtual, but get called directly in the linux binary.. + // Need to add a detour. + "CHLTVDemoRecorder::StartRecording" + { + "library" "engine" + "linux" "@_ZN17CHLTVDemoRecorder14StartRecordingEPKcb" + } + + "CHLTVDemoRecorder::StopRecording" + { + "library" "engine" + "linux" "@_ZN17CHLTVDemoRecorder13StopRecordingEv" + } } } } \ No newline at end of file