diff --git a/AMBuildScript b/AMBuildScript index 0e12f9c0..8c43f3fb 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -553,25 +553,7 @@ class SMConfig(object): if builder.target.platform == 'linux': if sdk.name in ['csgo', 'blade']: compiler.linkflags.remove('-static-libstdc++') - compiler.linkflags += ['-lstdc++'] compiler.defines += ['_GLIBCXX_USE_CXX11_ABI=0'] - elif builder.target.platform == 'mac': - if sdk.name in ['csgo']: - # Switch libc++ to libstdc++ for protobuf linkage. - compiler.cxxflags.remove('-stdlib=libc++') - compiler.linkflags.remove('-stdlib=libc++') - compiler.linkflags.remove('-lc++') - - compiler.cxxflags += ['-stdlib=libstdc++'] - compiler.linkflags += ['-stdlib=libstdc++'] - compiler.linkflags += ['-lstdc++'] - - if 'c++1y' in compiler.cxxflags: - compiler.cxxflags.remove('-std=c++1y') - compiler.cxxflags += ['-std=c++11'] - elif 'c++14' in compiler.cxxflags: - compiler.cxxflags.remove('-std=c++14') - compiler.cxxflags += ['-std=c++11'] for path in paths: compiler.cxxincludes += [os.path.join(sdk.path, *path)] diff --git a/core/AMBuilder b/core/AMBuilder index f77a2cbf..63079d16 100644 --- a/core/AMBuilder +++ b/core/AMBuilder @@ -4,42 +4,43 @@ import os project = SM.HL2Project(builder, 'sourcemod') project.sources += [ - 'MenuStyle_Valve.cpp', - 'logic_bridge.cpp', - 'smn_entities.cpp', - 'sm_stringutil.cpp', - 'MenuVoting.cpp', - 'smn_events.cpp', - 'frame_hooks.cpp', - 'smn_nextmap.cpp', - 'sourcemm_api.cpp', 'ChatTriggers.cpp', - 'smn_player.cpp', - 'sourcemod.cpp', - 'concmd_cleaner.cpp', - 'HalfLife2.cpp', - 'NextMap.cpp', 'ConCmdManager.cpp', 'ConVarManager.cpp', + 'ConsoleDetours.cpp', + 'CoreConfig.cpp', + 'EventManager.cpp', + 'GameHooks.cpp', + 'HalfLife2.cpp', + 'Logger.cpp', + 'MenuManager.cpp', + 'MenuStyle_Base.cpp', + 'MenuStyle_Radio.cpp', + 'MenuStyle_Valve.cpp', + 'MenuVoting.cpp', + 'NextMap.cpp', 'PlayerManager.cpp', 'TimerSys.cpp', - 'CoreConfig.cpp', - 'Logger.cpp', - 'smn_halflife.cpp', - 'smn_console.cpp', 'UserMessages.cpp', - 'MenuManager.cpp', - 'smn_hudtext.cpp', - 'smn_usermsgs.cpp', - 'MenuStyle_Base.cpp', - 'smn_keyvalues.cpp', - 'smn_vector.cpp', - 'EventManager.cpp', - 'MenuStyle_Radio.cpp', + 'concmd_cleaner.cpp', + 'frame_hooks.cpp', + 'logic_bridge.cpp', + 'pb_handle.cpp', 'sm_autonatives.cpp', - 'ConsoleDetours.cpp', + 'sm_stringutil.cpp', 'smn_commandline.cpp', - 'GameHooks.cpp', + 'smn_console.cpp', + 'smn_entities.cpp', + 'smn_events.cpp', + 'smn_halflife.cpp', + 'smn_hudtext.cpp', + 'smn_keyvalues.cpp', + 'smn_nextmap.cpp', + 'smn_player.cpp', + 'smn_usermsgs.cpp', + 'smn_vector.cpp', + 'sourcemm_api.cpp', + 'sourcemod.cpp', ] for sdk_name in SM.sdks: @@ -57,18 +58,20 @@ for sdk_name in SM.sdks: builder.sourcePath ] + pb_includes = [] if sdk.name == 'csgo': - compiler.cxxincludes += [ + pb_includes = [ os.path.join(sdk.path, 'common', 'protobuf-2.5.0', 'src'), os.path.join(sdk.path, 'public', 'engine', 'protobuf'), os.path.join(sdk.path, 'public', 'game', 'shared', 'csgo', 'protobuf') ] elif sdk.name == 'blade': - compiler.cxxincludes += [ + pb_includes = [ os.path.join(sdk.path, 'common', 'protobuf-2.5.0', 'src'), os.path.join(sdk.path, 'public', 'engine', 'protobuf'), os.path.join(sdk.path, 'public', 'game', 'shared', 'berimbau', 'protobuf') ] + compiler.cxxincludes += pb_includes if compiler.like('msvc'): compiler.defines += ['_ALLOW_KEYWORD_MACROS'] @@ -84,9 +87,9 @@ for sdk_name in SM.sdks: compiler.linkflags += ['-Wl,--exclude-libs=libprotobuf.a'] elif builder.target.platform == 'mac': if arch == 'x86': - lib_path = os.path.join(sdk.path, 'lib', 'osx32', 'release', 'libprotobuf.a') + lib_path = os.path.join(sdk.path, 'lib', 'osx32', 'release', 'libprotobuf-libcxx.a') elif arch == 'x64': - lib_path = os.path.join(sdk.path, 'lib', 'osx64', 'release', 'libprotobuf.a') + lib_path = os.path.join(sdk.path, 'lib', 'osx64', 'release', 'libprotobuf-libcxx.a') elif builder.target.platform == 'windows': msvc_ver = compiler.version vs_year = '' @@ -111,18 +114,53 @@ for sdk_name in SM.sdks: 'vprof_tool.cpp', ] + pb_sources = [] if sdk.name == 'csgo': - binary.sources += [ + pb_sources = [ os.path.join(sdk.path, 'public', 'engine', 'protobuf', 'netmessages.pb.cc'), os.path.join(sdk.path, 'public', 'game', 'shared', 'csgo', 'protobuf', 'cstrike15_usermessages.pb.cc'), os.path.join(sdk.path, 'public', 'game', 'shared', 'csgo', 'protobuf', 'cstrike15_usermessage_helpers.cpp'), ] elif sdk.name == 'blade': - binary.sources += [ + pb_sources = [ os.path.join(sdk.path, 'public', 'engine', 'protobuf', 'netmessages.pb.cc'), os.path.join(sdk.path, 'public', 'game', 'shared', 'berimbau', 'protobuf', 'berimbau_usermessages.pb.cc'), os.path.join(sdk.path, 'public', 'game', 'shared', 'berimbau', 'protobuf', 'berimbau_usermessage_helpers.cpp'), ] + if len(pb_sources): + binary.sources += pb_sources + binary.compiler.cxxdefines += ['PROTOBUF_ENABLE'] + + if builder.target.platform == 'mac' and sdk.name in ['csgo']: + # We need a proxy library since the game uses libstdc++. + pb_binary = SM.HL2Library(builder, 'pbproxy.' + sdk.ext, sdk, arch) + pb_binary.sources += pb_sources + pb_binary.sources += ['pb_proxy.cpp'] + pb_binary.compiler.cxxincludes += pb_includes + + # Switch from libc++ to libstdc++. + pb_binary.compiler.cxxflags.remove('-stdlib=libc++') + pb_binary.compiler.linkflags.remove('-lc++') + pb_binary.compiler.linkflags.remove('-stdlib=libc++') + pb_binary.compiler.cxxflags.append('-stdlib=libstdc++') + pb_binary.compiler.linkflags.append('-lstdc++') + pb_binary.compiler.linkflags.append('-stdlib=libstdc++') + if '-std=c++1y' in pb_binary.compiler.cxxflags: + pb_binary.compiler.cxxflags.remove('-std=c++1y') + elif '-std=c++14' in pb_binary.compiler.cxxflags: + pb_binary.compiler.cxxflags.remove('-std=c++14') + + if arch == 'x86': + pb_lib_path = os.path.join(sdk.path, 'lib', 'osx32', 'release', 'libprotobuf.a') + elif arch == 'x64': + pb_lib_path = os.path.join(sdk.path, 'lib', 'osx64', 'release', 'libprotobuf.a') + pb_binary.compiler.linkflags.append(pb_lib_path) + + SM.binaries += [builder.Add(pb_binary)] + + binary.compiler.cxxdefines += [ + 'PROTOBUF_PROXY_ENABLE', + 'PROTOBUF_PROXY_BINARY_NAME="pbproxy.{}"'.format(sdk.ext), + ] SM.binaries += builder.Add(project) - diff --git a/core/UserMessages.cpp b/core/UserMessages.cpp index 8c8d9b0d..92209045 100644 --- a/core/UserMessages.cpp +++ b/core/UserMessages.cpp @@ -42,6 +42,10 @@ UserMessages g_UserMsgs; +#ifdef USE_PROTOBUF_USERMESSAGES +const protobuf::Message *GetMessagePrototype(int msg_type); +#endif + #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE SH_DECL_HOOK3_void(IVEngineServer, SendUserMessage, SH_NOATTRIB, 0, IRecipientFilter &, int, const protobuf::Message &); #else @@ -58,7 +62,7 @@ UserMessages::UserMessages() : m_InterceptBuffer(m_pBase, 2500) { #else - : m_InterceptBuffer(NULL) + : m_InterceptBuffer(nullptr) { #endif m_HookCount = 0; @@ -233,7 +237,7 @@ google::protobuf::Message *UserMessages::StartProtobufMessage(int msg_id, const #ifndef USE_PROTOBUF_USERMESSAGES return NULL; #else - protobuf::Message *buffer; + PbHandle buffer; if (m_InExec || m_InHook) { @@ -262,8 +266,9 @@ google::protobuf::Message *UserMessages::StartProtobufMessage(int msg_id, const if (m_CurFlags & USERMSG_BLOCKHOOKS) { // direct message creation, return buffer "from engine". keep track - m_FakeEngineBuffer = GetMessagePrototype(msg_id)->New(); - buffer = m_FakeEngineBuffer; + m_FakeEngineBuffer = + PbHandle(GetMessagePrototype(msg_id)->New(), PbHandle::Owned, PbHandle::Local); + buffer = m_FakeEngineBuffer.AsUnowned(); } else { char messageName[32]; if (!GetMessageName(msg_id, messageName, sizeof(messageName))) @@ -272,27 +277,30 @@ google::protobuf::Message *UserMessages::StartProtobufMessage(int msg_id, const return NULL; } - protobuf::Message *msg = OnStartMessage_Pre(static_cast(&m_CellRecFilter), msg_id, messageName); + // The message returned here should always local. + auto msg = OnStartMessage_Pre(static_cast(&m_CellRecFilter), msg_id, messageName); switch (m_FakeMetaRes) { case MRES_IGNORED: case MRES_HANDLED: - m_FakeEngineBuffer = GetMessagePrototype(msg_id)->New(); - buffer = m_FakeEngineBuffer; + m_FakeEngineBuffer = + PbHandle(GetMessagePrototype(msg_id)->New(), PbHandle::Owned, PbHandle::Local); + buffer = m_FakeEngineBuffer.AsUnowned(); break; - case MRES_OVERRIDE: - m_FakeEngineBuffer = GetMessagePrototype(msg_id)->New(); + m_FakeEngineBuffer = + PbHandle(GetMessagePrototype(msg_id)->New(), PbHandle::Owned, PbHandle::Local); // fallthrough case MRES_SUPERCEDE: - buffer = msg; + buffer = std::move(msg); break; } OnStartMessage_Post(static_cast(&m_CellRecFilter), msg_id, messageName); } - return buffer; + assert(!buffer.is_owned()); + return buffer.GetLocalMessage(); #endif // USE_PROTOBUF_USERMESSAGES } @@ -306,9 +314,10 @@ bool UserMessages::EndMessage() #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE if (m_CurFlags & USERMSG_BLOCKHOOKS) { - ENGINE_CALL(SendUserMessage)(static_cast(m_CellRecFilter), m_CurId, *m_FakeEngineBuffer); - delete m_FakeEngineBuffer; - m_FakeEngineBuffer = NULL; + PbHandle priv = m_FakeEngineBuffer.ToPrivate(m_CurId); + ENGINE_CALL(SendUserMessage)(static_cast(m_CellRecFilter), m_CurId, + *priv.GetPrivateMessage()); + m_FakeEngineBuffer = nullptr; } else { OnMessageEnd_Pre(); @@ -317,10 +326,13 @@ bool UserMessages::EndMessage() case MRES_IGNORED: case MRES_HANDLED: case MRES_OVERRIDE: - engine->SendUserMessage(static_cast(m_CellRecFilter), m_CurId, *m_FakeEngineBuffer); - delete m_FakeEngineBuffer; - m_FakeEngineBuffer = NULL; + { + PbHandle priv = m_FakeEngineBuffer.ToPrivate(m_CurId); + engine->SendUserMessage(static_cast(m_CellRecFilter), m_CurId, + *priv.GetPrivateMessage()); + m_FakeEngineBuffer = nullptr; break; + } //case MRES_SUPERCEDE: } @@ -440,7 +452,7 @@ bool UserMessages::InternalHook(int msg_id, IBitBufUserMessageListener *pListene } #ifdef USE_PROTOBUF_USERMESSAGES -const protobuf::Message *UserMessages::GetMessagePrototype(int msg_type) +const protobuf::Message *GetMessagePrototype(int msg_type) { #if SOURCE_ENGINE == SE_CSGO return g_Cstrike15UsermessageHelpers.GetPrototype(msg_type); @@ -527,7 +539,8 @@ void UserMessages::OnSendUserMessage_Pre(IRecipientFilter &filter, int msg_type, } else { - m_FakeEngineBuffer = &const_cast(msg); + m_FakeEngineBuffer = + PbHandle(&const_cast(msg), PbHandle::Unowned, PbHandle::Private); } OnStartMessage_Post(&filter, msg_type, pszName); @@ -563,7 +576,7 @@ void UserMessages::OnSendUserMessage_Post(IRecipientFilter &filter, int msg_type #endif #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE -protobuf::Message *UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name) +PbHandle UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name) #elif SOURCE_ENGINE >= SE_LEFT4DEAD bf_write *UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name) #else @@ -577,7 +590,7 @@ bf_write *UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_typ || (m_InExec && (m_CurFlags & USERMSG_BLOCKHOOKS))) { m_InHook = false; - UM_RETURN_META_VALUE(MRES_IGNORED, NULL); + UM_RETURN_META_VALUE(MRES_IGNORED, nullptr); } m_CurId = msg_type; @@ -588,22 +601,20 @@ bf_write *UserMessages::OnStartMessage_Pre(IRecipientFilter *filter, int msg_typ if (!is_intercept_empty) { #ifdef USE_PROTOBUF_USERMESSAGES - if (m_InterceptBuffer) - delete m_InterceptBuffer; - m_InterceptBuffer = GetMessagePrototype(msg_type)->New(); - - UM_RETURN_META_VALUE(MRES_SUPERCEDE, m_InterceptBuffer); + m_InterceptBuffer = + PbHandle(GetMessagePrototype(msg_type)->New(), PbHandle::Owned, PbHandle::Local); + UM_RETURN_META_VALUE(MRES_SUPERCEDE, m_InterceptBuffer.AsUnowned()); #else m_InterceptBuffer.Reset(); UM_RETURN_META_VALUE(MRES_SUPERCEDE, &m_InterceptBuffer); #endif } - UM_RETURN_META_VALUE(MRES_IGNORED, NULL); + UM_RETURN_META_VALUE(MRES_IGNORED, nullptr); } #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE -protobuf::Message *UserMessages::OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name) +void* UserMessages::OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name) #elif SOURCE_ENGINE >= SE_LEFT4DEAD bf_write *UserMessages::OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name) #else @@ -617,9 +628,9 @@ bf_write *UserMessages::OnStartMessage_Post(IRecipientFilter *filter, int msg_ty #ifdef USE_PROTOBUF_USERMESSAGES if (m_FakeMetaRes == MRES_SUPERCEDE) - m_OrigBuffer = m_InterceptBuffer; + m_OrigBuffer = m_InterceptBuffer.AsUnowned(); else - m_OrigBuffer = m_FakeEngineBuffer; + m_OrigBuffer = m_FakeEngineBuffer.AsUnowned(); #else m_OrigBuffer = META_RESULT_ORIG_RET(bf_write *); #endif @@ -715,8 +726,11 @@ void UserMessages::OnMessageEnd_Pre() { pInfo = (*iter); pInfo->IsHooked = true; + #ifdef USE_PROTOBUF_USERMESSAGES - res = pInfo->Callback->InterceptUserMessage(m_CurId, m_InterceptBuffer, m_CurRecFilter); + PbHandle local = m_InterceptBuffer.ToLocal(m_CurId); + res = pInfo->Callback->InterceptUserMessage(m_CurId, local.GetLocalMessage(), m_CurRecFilter); + m_InterceptBuffer.CopyFrom(local); #else res = pInfo->Callback->InterceptUserMessage(m_CurId, &m_InterceptBuffer, m_CurRecFilter); #endif @@ -768,7 +782,8 @@ void UserMessages::OnMessageEnd_Pre() if (!handled && intercepted) { #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE - ENGINE_CALL(SendUserMessage)(static_cast(*m_CurRecFilter), m_CurId, *m_InterceptBuffer); + PbHandle priv = m_InterceptBuffer.ToPrivate(m_CurId); + ENGINE_CALL(SendUserMessage)(static_cast(*m_CurRecFilter), m_CurId, *priv.GetPrivateMessage()); #else bf_write *engine_bfw; #if SOURCE_ENGINE >= SE_LEFT4DEAD @@ -784,12 +799,9 @@ void UserMessages::OnMessageEnd_Pre() { #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE - int size = m_OrigBuffer->ByteSize(); - uint8 *data = (uint8 *)stackalloc(size); - m_OrigBuffer->SerializePartialToArray(data, size); - - protobuf::Message *pTempMsg = GetMessagePrototype(m_CurId)->New(); - pTempMsg->ParsePartialFromArray(data, size); + PbHandle tmp_msg(GetMessagePrototype(m_CurId)->New(), PbHandle::Owned, PbHandle::Local); + tmp_msg.CopyFrom(m_OrigBuffer); + auto pTempMsg = tmp_msg.GetLocalMessage(); #else bf_write *pTempMsg = m_OrigBuffer; #endif @@ -812,10 +824,6 @@ void UserMessages::OnMessageEnd_Pre() pInfo->IsHooked = false; iter++; } - -#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE - delete pTempMsg; -#endif } UM_RETURN_META((intercepted) ? MRES_SUPERCEDE : MRES_IGNORED); diff --git a/core/UserMessages.h b/core/UserMessages.h index 7c1a2a9b..96e218ae 100644 --- a/core/UserMessages.h +++ b/core/UserMessages.h @@ -52,6 +52,7 @@ using namespace SourceMod; #include #include #include +#include "pb_handle.h" using namespace google; #else @@ -108,8 +109,8 @@ public: #endif #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE - protobuf::Message *OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name); - protobuf::Message *OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name); + PbHandle OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name); + void* OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name); #elif SOURCE_ENGINE >= SE_LEFT4DEAD bf_write *OnStartMessage_Pre(IRecipientFilter *filter, int msg_type, const char *msg_name); bf_write *OnStartMessage_Post(IRecipientFilter *filter, int msg_type, const char *msg_name); @@ -121,7 +122,6 @@ public: void OnMessageEnd_Post(); private: #ifdef USE_PROTOBUF_USERMESSAGES - const protobuf::Message *GetMessagePrototype(int msg_type); bool InternalHook(int msg_id, IProtobufUserMessageListener *pListener, bool intercept, bool isNew); bool InternalUnhook(int msg_id, IProtobufUserMessageListener *pListener, bool intercept, bool isNew); #else @@ -141,11 +141,11 @@ private: bf_read m_ReadBuffer; #else // The engine used to provide this. Now we track it. - protobuf::Message *m_OrigBuffer; - protobuf::Message *m_FakeEngineBuffer; + PbHandle m_OrigBuffer; + PbHandle m_FakeEngineBuffer; META_RES m_FakeMetaRes; - protobuf::Message *m_InterceptBuffer; + PbHandle m_InterceptBuffer; #endif size_t m_HookCount; bool m_InHook; diff --git a/core/logic_bridge.cpp b/core/logic_bridge.cpp index 729599d3..c8fe82c6 100644 --- a/core/logic_bridge.cpp +++ b/core/logic_bridge.cpp @@ -51,6 +51,9 @@ #include #include #include +#if PROTOBUF_PROXY_ENABLE +# include "pb_handle.h" +#endif sm_logic_t logicore; @@ -653,6 +656,41 @@ void CoreProviderImpl::InitializeBridge() rootmenu = logicore.rootmenu; } +bool CoreProviderImpl::LoadProtobufProxy(char *error, size_t maxlength) +{ +#if !defined(PROTOBUF_PROXY_ENABLE) + return false; +#else + char file[PLATFORM_MAX_PATH]; + +#if !defined(PROTOBUF_PROXY_BINARY_NAME) +# error "No engine suffix defined" +#endif + + /* Now it's time to load the logic binary */ + g_SMAPI->PathFormat(file, + sizeof(file), + "%s/bin/" PLATFORM_ARCH_FOLDER PROTOBUF_PROXY_BINARY_NAME PLATFORM_LIB_EXT, + g_SourceMod.GetSourceModPath()); + + char myerror[255]; + pbproxy_ = ke::SharedLib::Open(file, myerror, sizeof(myerror)); + if (!pbproxy_) { + ke::SafeSprintf(error, maxlength, "failed to load %s: %s", file, myerror); + return false; + } + + auto fn = pbproxy_->get("GetProtobufProxy"); + if (!fn) { + ke::SafeStrcpy(error, maxlength, "could not find GetProtobufProxy function"); + return false; + } + + gProtobufProxy = fn(); + return true; +#endif +} + bool CoreProviderImpl::LoadBridge(char *error, size_t maxlength) { char file[PLATFORM_MAX_PATH]; diff --git a/core/pb_handle.cpp b/core/pb_handle.cpp new file mode 100644 index 00000000..85915fd7 --- /dev/null +++ b/core/pb_handle.cpp @@ -0,0 +1,35 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2020 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#include "pb_handle.h" + +#if defined PROTOBUF_PROXY_ENABLE +IProtobufProxy* gProtobufProxy = nullptr; +#endif diff --git a/core/pb_handle.h b/core/pb_handle.h new file mode 100644 index 00000000..73e6f434 --- /dev/null +++ b/core/pb_handle.h @@ -0,0 +1,216 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2020 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#pragma once + +#if defined(PROTOBUF_ENABLE) +#include + +#if defined(PROTOBUF_PROXY_ENABLE) +# include "pb_proxy.h" + +extern IProtobufProxy* gProtobufProxy; +#endif + +const google::protobuf::Message *GetMessagePrototype(int msg_type); + +// On SDKs which use protobufs, the engine has objects compiled against a specific +// version of protobuf. Normally this is fine, we take care on Linux to use the +// same C++ ABI. On macOS however, we use libc++ to enable C++11 functionality, +// whereas the protobuf library has been compiled with libstc++. These ABIs are +// not compatible. +// +// To address the problem, we introduce PbHandle. PbHandle is a wrapper around +// protobuf::Message with two added pieces of state: whether or not the handle +// "owns" the message (and can free it in its destructor), and whether or not +// the handle was created by the engine (private) or created by SourceMod +// (local). +// +// Whenever we transfer a protobuf::Message pointer to SourceMod, we must take +// care to convert it to a Local version first. Whenever we transfer a protobuf +// pointer to the engine, we must convert it to a Private handle. +// +// For platforms with no ABI differences (almost all of them), the handle is a +// no-op. The private and local localities are compatible and no translation +// takes place. +// +// On macOS, CS:GO does require translation. SourceMod loads a tiny shim +// library that contains a copy of the protobuf sources compiled against the +// game's ABI. It then provides serialization and deserialization methods. +// SourceMod must not interact with the game's protobuf objects without first +// going through this proxy library. +// +// Note that PbHandle is not quite like unique_ptr_. It can be converted into a +// PbHandle that does not destroy the underlying object. This is mainly because +// UserMessages.cpp has rather complex state, so it is useful to track locality +// without destroying an object. An unowned PbHandle must not outlive the +// owning PbHandle. +class PbHandle +{ +public: + enum Ownership { + Owned, + Unowned, + }; + enum Locality { + Local, + Private, + }; + + PbHandle() : msg_(nullptr) {} + PbHandle(decltype(nullptr)) : msg_(nullptr) {} + PbHandle(google::protobuf::Message* msg, Ownership ownership, Locality locality) + : msg_(msg), + ownership_(ownership), + locality_(locality) + {} + PbHandle(PbHandle&& other) + : msg_(other.msg_), + ownership_(other.ownership_), + locality_(other.locality_) + { + other.msg_ = nullptr; + } + PbHandle(const PbHandle&) = delete; + + ~PbHandle() { + maybe_free(); + } + + PbHandle& operator =(PbHandle&& other) { + maybe_free(); + msg_ = other.msg_; + ownership_ = other.ownership_; + locality_ = other.locality_; + other.msg_ = nullptr; + return *this; + } + PbHandle& operator =(const PbHandle&) = delete; + + google::protobuf::Message* operator ->() const { return msg_; } + + google::protobuf::Message* GetLocalMessage() { + assert(locality_ == Local); + return msg_; + } + google::protobuf::Message* GetPrivateMessage() { + assert(locality_ == Private); + return msg_; + } + + PbHandle ToLocal(int msg_type) { + if (locality_ == Local) + return AsUnowned(); +#if defined(PROTOBUF_PROXY_ENABLE) + PbHandle local(GetMessagePrototype(msg_type)->New(), Owned, Local); + local.CopyFromPrivate(msg_); + return local; +#else + return PbHandle(msg_, Unowned, Local); +#endif + } + + PbHandle ToPrivate(int msg_type) { + if (locality_ == Private) + return AsUnowned(); +#if defined(PROTOBUF_PROXY_ENABLE) + PbHandle priv(gProtobufProxy->NewPrototype(msg_type), Owned, Private); + priv.CopyFromLocal(msg_); + return priv; +#else + return PbHandle(msg_, Unowned, Private); +#endif + } + + void CopyFrom(const PbHandle& other) { + if (other.msg_ == msg_) { + assert(other.locality_ == locality_); + return; + } +#if defined(PROTOBUF_PROXY_ENABLE) + if (other.locality_ == Local) + CopyFromLocal(other.msg_); + else if (other.locality_ == Private) + CopyFromPrivate(other.msg_); +#else + msg_->CopyFrom(*other.msg_); +#endif + } + + PbHandle AsUnowned() { + return PbHandle(msg_, Unowned, locality_); + } + + bool is_owned() const { return ownership_ == Owned; } + bool is_local() const { return locality_ == Local; } + +private: +#if defined(PROTOBUF_PROXY_ENABLE) + void CopyFromPrivate(google::protobuf::Message* message) { + void* out; + size_t len; + if (!gProtobufProxy->Serialize(message, &out, &len)) + return; + if (locality_ == Local) + msg_->ParsePartialFromArray(out, len); + else + gProtobufProxy->Deserialize(out, len, msg_); + gProtobufProxy->FreeBuffer(out); + } + + void CopyFromLocal(google::protobuf::Message* message) { + if (locality_ == Local) { + msg_->CopyFrom(*message); + } else { + auto data = message->SerializePartialAsString(); + gProtobufProxy->Deserialize(data.data(), data.size(), msg_); + } + } +#endif + + void maybe_free() { + if (ownership_ != Owned) + return; +#if defined(PROTOBUF_PROXY_ENABLE) + if (locality_ == Private) { + gProtobufProxy->FreeMessage(msg_); + return; + } +#endif + delete msg_; + } + +private: + google::protobuf::Message* msg_ = nullptr; + Ownership ownership_ = Unowned; + Locality locality_ = Local; +}; + +#endif // PROTOBUF_ENABLE diff --git a/core/pb_proxy.cpp b/core/pb_proxy.cpp new file mode 100644 index 00000000..886cafa4 --- /dev/null +++ b/core/pb_proxy.cpp @@ -0,0 +1,100 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2020 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#include "pb_proxy.h" + +#include +#include + +#if SOURCE_ENGINE == SE_CSGO +# include +#else +# error "No source engine compatibility" +#endif + +class ProtobufProxy : public IProtobufProxy +{ +public: + bool Serialize(const google::protobuf::Message* message, void** out, size_t* len); + void FreeBuffer(void* data); + bool Deserialize(const void* buffer, size_t len, google::protobuf::Message* message); + google::protobuf::Message* NewPrototype(int msg_type); + void FreeMessage(google::protobuf::Message* message); +}; + +static ProtobufProxy sProtobufProxy; + +PLATFORM_EXTERN_C IProtobufProxy* +GetProtobufProxy() +{ + return &sProtobufProxy; +} + +bool +ProtobufProxy::Serialize(const google::protobuf::Message* message, void** out, size_t* len) +{ + *len = message->ByteSize(); + *out = malloc(*len); + if (!*out) + return false; + if (!message->SerializePartialToArray(*out, *len)) { + free(*out); + return false; + } + return true; +} + +void +ProtobufProxy::FreeBuffer(void* data) +{ + free(data); +} + +bool +ProtobufProxy::Deserialize(const void* buffer, size_t len, google::protobuf::Message* message) +{ + return message->ParsePartialFromArray(buffer, len); +} + +google::protobuf::Message* +ProtobufProxy::NewPrototype(int msg_type) +{ +#if SOURCE_ENGINE == SE_CSGO + return g_Cstrike15UsermessageHelpers.GetPrototype(msg_type)->New(); +#else +# error "No source engine compatibility." +#endif +} + +void +ProtobufProxy::FreeMessage(google::protobuf::Message* message) +{ + delete message; +} diff --git a/core/pb_proxy.h b/core/pb_proxy.h new file mode 100644 index 00000000..5810c36d --- /dev/null +++ b/core/pb_proxy.h @@ -0,0 +1,58 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2020 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#pragma once + +#include + +class IProtobufProxy +{ +public: + // Serialize the given message into a buffer. The buffer and its length are placed in |out|. + // The buffer must be freed with FreeBuffer. + virtual bool Serialize(const google::protobuf::Message* message, void** out, size_t* len) = 0; + virtual void FreeBuffer(void* data) = 0; + + // Deserialize the given buffer into a message. + virtual bool Deserialize(const void* buffer, size_t len, google::protobuf::Message* message) = 0; + + // Allocate/free a prototype. + virtual google::protobuf::Message* NewPrototype(int msg_type) = 0; + virtual void FreeMessage(google::protobuf::Message* message) = 0; + + bool Serialize(const google::protobuf::Message& message, void** out, size_t* len) { + return Serialize(&message, out, len); + } + bool Deserialize(const void* buffer, size_t len, google::protobuf::Message& message) { + return Deserialize(buffer, len, &message); + } +}; + +typedef IProtobufProxy*(*GetProtobufProxyFn)(); diff --git a/core/provider.h b/core/provider.h index ade45f00..ff5d670e 100644 --- a/core/provider.h +++ b/core/provider.h @@ -38,6 +38,7 @@ public: // Local functions. void InitializeBridge(); + bool LoadProtobufProxy(char *error, size_t maxlength); bool LoadBridge(char *error, size_t maxlength); void ShutdownBridge(); @@ -75,6 +76,7 @@ public: } private: + ke::RefPtr pbproxy_; ke::RefPtr logic_; LogicInitFunction logic_init_; GameHooks hooks_;