Introduce a pbproxy library to solve macOS linker issues.

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.
This commit is contained in:
David Anderson 2020-05-22 22:57:42 -07:00
parent d525b466ec
commit e5ddbd9886
10 changed files with 578 additions and 101 deletions

View File

@ -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)]

View File

@ -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)

View File

@ -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<IRecipientFilter *>(&m_CellRecFilter), msg_id, messageName);
// The message returned here should always local.
auto msg = OnStartMessage_Pre(static_cast<IRecipientFilter *>(&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<IRecipientFilter *>(&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<IRecipientFilter &>(m_CellRecFilter), m_CurId, *m_FakeEngineBuffer);
delete m_FakeEngineBuffer;
m_FakeEngineBuffer = NULL;
PbHandle priv = m_FakeEngineBuffer.ToPrivate(m_CurId);
ENGINE_CALL(SendUserMessage)(static_cast<IRecipientFilter &>(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<IRecipientFilter &>(m_CellRecFilter), m_CurId, *m_FakeEngineBuffer);
delete m_FakeEngineBuffer;
m_FakeEngineBuffer = NULL;
{
PbHandle priv = m_FakeEngineBuffer.ToPrivate(m_CurId);
engine->SendUserMessage(static_cast<IRecipientFilter &>(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<protobuf::Message &>(msg);
m_FakeEngineBuffer =
PbHandle(&const_cast<protobuf::Message &>(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<IRecipientFilter &>(*m_CurRecFilter), m_CurId, *m_InterceptBuffer);
PbHandle priv = m_InterceptBuffer.ToPrivate(m_CurId);
ENGINE_CALL(SendUserMessage)(static_cast<IRecipientFilter &>(*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);

View File

@ -52,6 +52,7 @@ using namespace SourceMod;
#include <google/protobuf/message.h>
#include <google/protobuf/descriptor.h>
#include <netmessages.pb.h>
#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;

View File

@ -51,6 +51,9 @@
#include <bridge/include/IVEngineServerBridge.h>
#include <bridge/include/IPlayerInfoBridge.h>
#include <bridge/include/IFileSystemBridge.h>
#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<GetProtobufProxyFn>("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];

35
core/pb_handle.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "pb_handle.h"
#if defined PROTOBUF_PROXY_ENABLE
IProtobufProxy* gProtobufProxy = nullptr;
#endif

216
core/pb_handle.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#pragma once
#if defined(PROTOBUF_ENABLE)
#include <google/protobuf/message.h>
#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

100
core/pb_proxy.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "pb_proxy.h"
#include <stdlib.h>
#include <sm_platform.h>
#if SOURCE_ENGINE == SE_CSGO
# include <cstrike15_usermessage_helpers.h>
#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;
}

58
core/pb_proxy.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#pragma once
#include <google/protobuf/message.h>
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)();

View File

@ -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<ke::SharedLib> pbproxy_;
ke::RefPtr<ke::SharedLib> logic_;
LogicInitFunction logic_init_;
GameHooks hooks_;