Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions managed/src/SwiftlyS2.Core/Bootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public static void Start(IntPtr nativeTable, int nativeTableSize, string basePat
.AddTraceManagerService()
.AddPermissionManager()
.AddCoreHookService()
.AddCoreCommandService()
.AddMenuService()
.AddCommandTrackerManager()
.AddCommandTrackerService()
Expand Down
136 changes: 93 additions & 43 deletions managed/src/SwiftlyS2.Core/Services/CoreHookService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@

namespace SwiftlyS2.Core.Services;

internal class CoreHookService : IDisposable {
internal class CoreHookService : IDisposable
{
private ILogger<CoreHookService> _Logger { get; init; }
private ISwiftlyCore _Core { get; init; }

public CoreHookService(ILogger<CoreHookService> logger, ISwiftlyCore core) {
public CoreHookService(ILogger<CoreHookService> logger, ISwiftlyCore core)
{
_Logger = logger;
_Core = core;

HookCanAcquire();
HookCommandExecute();
HookICVarFindConCommand();
Expand Down Expand Up @@ -56,14 +58,16 @@ __int64 sub_1C0CD0(__int64 a1, int a2, unsigned int a3, ...)
private IUnmanagedFunction<CanAcquireDelegate>? _CanAcquire;
private Guid _CanAcquireGuid;

private void HookCanAcquire() {
private void HookCanAcquire()
{

var address = _Core.GameData.GetSignature("CCSPlayer_ItemServices::CanAcquire");

_Logger.LogInformation("Hooking CCSPlayer_ItemServices::CanAcquire at {Address}", address);

_CanAcquire = _Core.Memory.GetUnmanagedFunctionByAddress<CanAcquireDelegate>(address);
_CanAcquireGuid = _CanAcquire.AddHook(next => {
_CanAcquireGuid = _CanAcquire.AddHook(next =>
{

return (pItemServices, pEconItemView, acquireMethod, unk1) =>
{
Expand All @@ -72,7 +76,8 @@ private void HookCanAcquire() {
var itemServices = _Core.Memory.ToSchemaClass<CCSPlayer_ItemServices>(pItemServices);
var econItemView = _Core.Memory.ToSchemaClass<CEconItemView>(pEconItemView);

var @event = new OnItemServicesCanAcquireHookEvent {
var @event = new OnItemServicesCanAcquireHookEvent
{
ItemServices = itemServices,
EconItemView = econItemView,
AcquireMethod = (AcquireMethod)acquireMethod,
Expand All @@ -81,7 +86,8 @@ private void HookCanAcquire() {

EventPublisher.InvokeOnCanAcquireHook(@event);

if (@event.Intercepted) {
if (@event.Intercepted)
{
// original result is modified here.
return (int)@event.OriginalResult;
}
Expand All @@ -91,7 +97,8 @@ private void HookCanAcquire() {
});
}

private void HookCommandExecute() {
private void HookCommandExecute()
{

var address = _Core.GameData.GetSignature("Cmd_ExecuteCommand");

Expand All @@ -103,80 +110,123 @@ private void HookCommandExecute() {
{
return (a1, a2, a3, a4, a5) =>
{
var (commandName, commandPtr) = (a5 != nint.Zero && a5 < nint.MaxValue && commandNameOffset != 0) switch {
var (commandName, commandPtr) = (a5 != nint.Zero && a5 < nint.MaxValue && commandNameOffset != 0) switch
{
true when Marshal.ReadIntPtr(new nint(a5 + commandNameOffset)) is var basePtr && basePtr != nint.Zero && basePtr < nint.MaxValue
=> (Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(basePtr)) ?? string.Empty, Marshal.ReadIntPtr(basePtr)),
_ => (string.Empty, nint.Zero)
};

var preEvent = new OnCommandExecuteHookEvent {
var preEvent = new OnCommandExecuteHookEvent
{
OriginalName = commandName,
HookMode = HookMode.Pre
};
EventPublisher.InvokeOnCommandExecuteHook(preEvent);

nint newCommandNamePtr = nint.Zero;

if (preEvent.Intercepted && preEvent.CommandName.Length < commandName.Length) {
if (preEvent.Intercepted && preEvent.CommandName.Length < commandName.Length)
{
var newCommandName = Encoding.UTF8.GetBytes(preEvent.CommandName);

newCommandNamePtr = Marshal.AllocHGlobal(newCommandName.Length + 1);
newCommandNamePtr = NativeAllocator.Alloc((ulong)(newCommandName.Length + 1));
newCommandNamePtr.Write(newCommandName.Length, 0);

newCommandNamePtr.CopyFrom(newCommandName);
(a5 + commandNameOffset).Read<nint>().Write(newCommandNamePtr);
}

var result = next()(a1, a2, a3, a4, a5);

var postEvent = new OnCommandExecuteHookEvent {

var postEvent = new OnCommandExecuteHookEvent
{
OriginalName = commandName,
HookMode = HookMode.Post
};
EventPublisher.InvokeOnCommandExecuteHook(postEvent);

if (newCommandNamePtr != nint.Zero) {
Marshal.FreeHGlobal(newCommandNamePtr);
if (newCommandNamePtr != nint.Zero)
{
NativeAllocator.Free(newCommandNamePtr);
}

return result;
};
});
}

private delegate nint FindConCommandDelegate(nint pICvar, nint pRet, nint pConCommandName, int unk1);
private IUnmanagedFunction<FindConCommandDelegate>? _FindConCommand;
private delegate nint FindConCommandDelegateLinux(nint pICvar, nint pConCommandName, int unk1);

private IUnmanagedFunction<FindConCommandDelegate>? _FindConCommandWindows;
private IUnmanagedFunction<FindConCommandDelegateLinux>? _FindConCommandLinux;
private Guid _FindConCommandGuid;

private void HookICVarFindConCommand() {

var icvar = _Core.Memory.GetInterfaceByName("VEngineCvar007")!;
var offset = _Core.GameData.GetOffset("ICvar::FindConCommand");
_FindConCommand = _Core.Memory.GetUnmanagedFunctionByVTable<FindConCommandDelegate>(icvar.Value.Read<nint>(), offset);

_Logger.LogInformation("Hooking ICvar::FindConCommand at {Address}", _FindConCommand.Address);

_FindConCommandGuid = _FindConCommand.AddHook((next) => {
return (pICvar, pRet, pConCommandName, unk1) => {
var commandName = Marshal.PtrToStringAnsi(pConCommandName)!;
if (commandName.StartsWith("^wb^")) {
commandName = commandName.Substring(4);
var bytes = Encoding.UTF8.GetBytes(commandName);
unsafe {
var pStr = (nint)NativeMemory.AllocZeroed((nuint)bytes.Length);
pStr.CopyFrom(bytes);
var result = next()(pICvar, pRet, pStr, unk1);
NativeMemory.Free((void*)pStr);
return result;
private void HookICVarFindConCommand()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var offset = _Core.GameData.GetOffset("ICvar::FindConCommand");
_FindConCommandLinux = _Core.Memory.GetUnmanagedFunctionByVTable<FindConCommandDelegateLinux>(_Core.Memory.GetVTableAddress("tier0", "CCvar")!.Value, offset);

_Logger.LogInformation("Hooking ICvar::FindConCommand at {Address}", _FindConCommandLinux.Address);

_FindConCommandGuid = _FindConCommandLinux.AddHook((next) =>
{
return (pICvar, pConCommandName, unk1) =>
{
var commandName = Marshal.PtrToStringAnsi(pConCommandName)!;
if (commandName.StartsWith("^wb^"))
{
commandName = commandName.Substring(4);
var bytes = Encoding.UTF8.GetBytes(commandName);
unsafe
{
var pStr = (nint)NativeMemory.AllocZeroed((nuint)bytes.Length);
pStr.CopyFrom(bytes);
var result = next()(pICvar, pStr, unk1);
NativeMemory.Free((void*)pStr);
return result;
}
}
}
return next()(pICvar, pRet, pConCommandName, unk1);
};
});
return next()(pICvar, pConCommandName, unk1);
};
});
}
else
{
var offset = _Core.GameData.GetOffset("ICvar::FindConCommand");
_FindConCommandWindows = _Core.Memory.GetUnmanagedFunctionByVTable<FindConCommandDelegate>(_Core.Memory.GetVTableAddress("tier0", "CCvar")!.Value, offset);

_Logger.LogInformation("Hooking ICvar::FindConCommand at {Address}", _FindConCommandWindows.Address);

_FindConCommandGuid = _FindConCommandWindows.AddHook((next) =>
{
return (pICvar, pRet, pConCommandName, unk1) =>
{
var commandName = Marshal.PtrToStringAnsi(pConCommandName)!;
if (commandName.StartsWith("^wb^"))
{
commandName = commandName.Substring(4);
var bytes = Encoding.UTF8.GetBytes(commandName);
unsafe
{
var pStr = (nint)NativeMemory.AllocZeroed((nuint)bytes.Length);
pStr.CopyFrom(bytes);
var result = next()(pICvar, pRet, pStr, unk1);
NativeMemory.Free((void*)pStr);
return result;
}
}
return next()(pICvar, pRet, pConCommandName, unk1);
};
});
}
}

public void Dispose() {
public void Dispose()
{
_CanAcquire!.RemoveHook(_CanAcquireGuid);
_ExecuteCommand!.RemoveHook(_ExecuteCommandGuid);
}
Expand Down
1 change: 1 addition & 0 deletions managed/src/SwiftlyS2.Core/Services/StartupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal class StartupService : IHostedService
public StartupService(IServiceProvider provider)
{
_provider = provider;
provider.UseCoreCommandService();
provider.UseCoreHookService();
provider.UsePermissionManager();
provider.UsePluginManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ public CTakeDamageInfo(CBaseEntity inflictor, CBaseEntity attacker, CBaseEntity
public HitGroup_t ActualHitGroup => Trace->HitBox->m_nGroupId;
}

[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential,Pack=8,Size=40)]
public unsafe struct CTakeDamageResult
{
public CTakeDamageInfo* OriginatingInfo;
public int HealthLost;
public int HealthBefore;
public int DamageDealt;
public float PreModifiedDamage;
public int TotalledHealthLost;
Expand Down
2 changes: 1 addition & 1 deletion plugin_files/gamedata/cs2/core/offsets.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
},
"IGameEventSystem::PostEventAbstract": {
"windows": 16,
"linux": 16
"linux": 15
},
"INetworkServerService::StartupServer": {
"windows": 26,
Expand Down
2 changes: 1 addition & 1 deletion plugin_files/gamedata/cs2/core/signatures.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"CBaseEntity::TakeDamage": {
"lib": "server",
"windows": "40 55 41 54 41 55 41 56 41 57 48 81 EC ? ? ? ? 48 8D 6C 24 ? 48 89 9D ? ? ? ? 45 33 ED",
"linux": "55 48 89 E5 41 57 41 56 49 89 F6 41 55 41 54 49 89 D4 53 48 89 FB 48 83 EC ? 48 85 D2"
"linux": "55 48 89 E5 41 57 41 56 49 89 F6 41 55 41 54 49 89 FC 53 48 89 D3 48 83 EC ? 48 85 D2"
},
// "Error - cannot add bots after game is over."
"BotNavIgnore1": {
Expand Down
6 changes: 0 additions & 6 deletions src/core/bridge/metamod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,12 @@ class GameSessionConfiguration_t
{
};

ICvar* g_pcVar = nullptr;

PLUGIN_EXPOSE(SwiftlyMMBridge, g_MMPluginBridge);
bool SwiftlyMMBridge::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool late)
{
PLUGIN_SAVEVARS();
g_SMAPI->AddListener(this, this);

GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION);

META_CONVAR_REGISTER(FCVAR_RELEASE | FCVAR_SERVER_CAN_EXECUTE | FCVAR_CLIENT_CAN_EXECUTE | FCVAR_GAMEDLL);

bool result = g_SwiftlyCore.Load(BridgeKind_t::Metamod);

if (late)
Expand Down
21 changes: 21 additions & 0 deletions src/core/entrypoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,17 @@
#include <api/shared/files.h>

#include <public/tier1/KeyValues.h>
#include <public/icvar.h>

#include <fmt/format.h>

#include <s2binlib/s2binlib.h>
#include <public/engine/igameeventsystem.h>

#include <public/steam/steam_gameserver.h>

#include <public/tier1/convar.h>

SwiftlyCore g_SwiftlyCore;
InterfacesManager g_ifaceService;
CSteamGameServerAPIContext g_SteamAPI;
Expand Down Expand Up @@ -74,6 +79,8 @@ void DestroyLoopModeHook(void* _this, void* loopmode);
bool LoopInitHook(void* _this, KeyValues* pKeyValues, void* pRegistry);
void LoopShutdownHook(void* _this);

extern ICvar* g_pCVar;

bool SwiftlyCore::Load(BridgeKind_t kind)
{
m_iKind = kind;
Expand All @@ -83,6 +90,10 @@ bool SwiftlyCore::Load(BridgeKind_t kind)
s2binlib_set_module_base_from_pointer("server", g_ifaceService.FetchInterface<IServerGameDLL>(INTERFACEVERSION_SERVERGAMEDLL));
s2binlib_set_module_base_from_pointer("engine2", g_ifaceService.FetchInterface<IVEngineServer2>(INTERFACEVERSION_VENGINESERVER));

auto cvars = g_ifaceService.FetchInterface<ICvar>(CVAR_INTERFACE_VERSION);
g_pCVar = cvars;
ConVar_Register(FCVAR_RELEASE | FCVAR_SERVER_CAN_EXECUTE | FCVAR_CLIENT_CAN_EXECUTE | FCVAR_GAMEDLL, nullptr, nullptr);

auto logger = g_ifaceService.FetchInterface<ILogger>(LOGGER_INTERFACE_VERSION);

if (GetCurrentGame() == "unknown") {
Expand Down Expand Up @@ -414,6 +425,16 @@ void* SwiftlyCore::GetInterface(const std::string& interface_name)
ifaceCreate = get_export(lib, "CreateInterface");
unload_library(lib);
}
else if (CVAR_INTERFACE_VERSION == interface_name) {
void* lib = load_library(
(const char_t*)WIN_LINUX(
StringWide(Plat_GetGameDirectory() + std::string("\\bin\\win64\\tier0.dll")).c_str(),
(Plat_GetGameDirectory() + std::string("/bin/linuxsteamrt64/libtier0.so")).c_str()
)
);
ifaceCreate = get_export(lib, "CreateInterface");
unload_library(lib);
}
else if (NETWORKMESSAGES_INTERFACE_VERSION == interface_name) {
void* lib = load_library(
(const char_t*)WIN_LINUX(
Expand Down
Loading