Skip to content

Commit

Permalink
feat: SKSE & MO2 policies.
Browse files Browse the repository at this point in the history
  • Loading branch information
Force67 committed May 7, 2022
1 parent 3b3664e commit 6b33705
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 38 deletions.
11 changes: 9 additions & 2 deletions Code/client/ScriptExtender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ constexpr size_t kScriptExtenderNameLength = sizeof(kScriptExtenderName) / sizeo
// Use this to raise the SKSE baseline
constexpr int kSKSEMinBuild = 20100;

HMODULE g_SKSEModuleHandle{nullptr};

struct FileVersion
{
static constexpr uint8_t scVersionSize = 4;
Expand Down Expand Up @@ -71,6 +73,11 @@ std::string GetSKSEStyleExeVersion()
}
} // namespace

bool IsScriptExtenderLoaded()
{
return g_SKSEModuleHandle;
}

void LoadScriptExender()
{
const auto exeVerson{GetSKSEStyleExeVersion()};
Expand Down Expand Up @@ -133,9 +140,9 @@ void LoadScriptExender()
}
#endif

if (auto* pSKSELibraryHandle = LoadLibraryW(needle->c_str()))
if (g_SKSEModuleHandle = LoadLibraryW(needle->c_str()))
{
if (auto* pStartSKSE = reinterpret_cast<void (*)()>(GetProcAddress(pSKSELibraryHandle, "StartSKSE")))
if (auto* pStartSKSE = reinterpret_cast<void (*)()>(GetProcAddress(g_SKSEModuleHandle, "StartSKSE")))
{
spdlog::info(
"Starting SKSE {}... be aware that messages that start without a colored [timestamp] prefix are "
Expand Down
5 changes: 3 additions & 2 deletions Code/client/ScriptExtender.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

// Utility to import the 3rd-party Script Extender made by Ian Patterson et al.
// Required for non-Creation Kit mods such as SkyUI

void LoadScriptExender();

// Check if the SE is active
bool IsScriptExtenderLoaded();
27 changes: 22 additions & 5 deletions Code/client/Services/Generic/TransportService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
#include <Messages/AuthenticationRequest.h>
#include <Messages/ServerMessageFactory.h>

#include <ScriptExtender.h>
#include <Services/DiscordService.h>

//#include <imgui_internal.h>

static constexpr wchar_t kMO2DllName[] = L"usvfs_x64.dll";

using TiltedPhoques::Packet;

TransportService::TransportService(World& aWorld, entt::dispatcher& aDispatcher) noexcept
Expand Down Expand Up @@ -98,12 +101,14 @@ void TransportService::OnConnected()
{
AuthenticationRequest request;
request.Version = BUILD_COMMIT;
request.SKSEActive = IsScriptExtenderLoaded();
request.MO2Active = GetModuleHandleW(kMO2DllName);

// null if discord is not active
// TODO: think about user opt out
request.DiscordId = m_world.ctx().at<DiscordService>().GetUser().id;
auto* pNpc = Cast<TESNPC>(PlayerCharacter::Get()->baseForm);
if (pNpc)

if (auto* pNpc = Cast<TESNPC>(PlayerCharacter::Get()->baseForm))
{
request.Username = pNpc->fullName.value.AsAscii();
}
Expand Down Expand Up @@ -148,9 +153,10 @@ void TransportService::HandleUpdate(const UpdateEvent& acEvent) noexcept

void TransportService::HandleAuthenticationResponse(const AuthenticationResponse& acMessage) noexcept
{
using AR = AuthenticationResponse::ResponseType;
switch (acMessage.Type)
{
case AuthenticationResponse::ResponseType::kAccepted:
case AR::kAccepted:
{
m_connected = true;
m_dispatcher.trigger(acMessage.UserMods);
Expand All @@ -159,10 +165,10 @@ void TransportService::HandleAuthenticationResponse(const AuthenticationResponse
break;
}
// TODO(Anyone): Handle this within the ui
case AuthenticationResponse::ResponseType::kWrongVersion:
case AR::kWrongVersion:
spdlog::error("This server expects version {} but you are on version {}", acMessage.Version, BUILD_COMMIT);
break;
case AuthenticationResponse::ResponseType::kMissingMods: {
case AR::kModsMismatch: {
spdlog::error("This server has ModPolicy enabled. You were kicked because you have the following mods installed:");
for (const auto& m : acMessage.UserMods.ModList)
{
Expand All @@ -171,6 +177,17 @@ void TransportService::HandleAuthenticationResponse(const AuthenticationResponse
spdlog::error("Please remove them to join");
break;
}
case AR::kClientModsDisallowed: {
TiltedPhoques::String reason = "This server disallows";

if (acMessage.SKSEActive)
reason += " SKSE";

if (acMessage.MO2Active)
reason += " MO2";
spdlog::error(reason.c_str());
break;
}
default:
spdlog::error("The server refused connection without reason.");
break;
Expand Down
4 changes: 4 additions & 0 deletions Code/encoding/Messages/AuthenticationRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
void AuthenticationRequest::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept
{
Serialization::WriteVarInt(aWriter, DiscordId);
Serialization::WriteBool(aWriter, SKSEActive);
Serialization::WriteBool(aWriter, MO2Active);
Serialization::WriteString(aWriter, Token);
Serialization::WriteString(aWriter, Version);
UserMods.Serialize(aWriter);
Expand All @@ -14,6 +16,8 @@ void AuthenticationRequest::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReade
ClientMessage::DeserializeRaw(aReader);

DiscordId = Serialization::ReadVarInt(aReader);
SKSEActive = Serialization::ReadBool(aReader);
MO2Active = Serialization::ReadBool(aReader);
Token = Serialization::ReadString(aReader);
Version = Serialization::ReadString(aReader);
UserMods.Deserialize(aReader);
Expand Down
13 changes: 6 additions & 7 deletions Code/encoding/Messages/AuthenticationRequest.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#pragma once

#include "Message.h"
#include <TiltedCore/Buffer.hpp>
#include <Structs/Mods.h>
#include <TiltedCore/Buffer.hpp>

struct AuthenticationRequest final : ClientMessage
{
Expand All @@ -19,15 +19,14 @@ struct AuthenticationRequest final : ClientMessage

bool operator==(const AuthenticationRequest& achRhs) const noexcept
{
return DiscordId == achRhs.DiscordId &&
Token == achRhs.Token &&
Version == achRhs.Version &&
UserMods == achRhs.UserMods &&
Username == achRhs.Username &&
GetOpcode() == achRhs.GetOpcode();
return GetOpcode() == achRhs.GetOpcode() && DiscordId == achRhs.DiscordId && SKSEActive == achRhs.SKSEActive &&
MO2Active == achRhs.MO2Active && Token == achRhs.Token && Version == achRhs.Version &&
UserMods == achRhs.UserMods && Username == achRhs.Username;
}

uint64_t DiscordId;
bool SKSEActive;
bool MO2Active;
String Token;
String Version;
Mods UserMods;
Expand Down
4 changes: 4 additions & 0 deletions Code/encoding/Messages/AuthenticationResponse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
void AuthenticationResponse::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept
{
Serialization::WriteVarInt(aWriter, static_cast<uint32_t>(Type));
Serialization::WriteBool(aWriter, SKSEActive);
Serialization::WriteBool(aWriter, MO2Active);
Serialization::WriteString(aWriter, Version);
UserMods.Serialize(aWriter);
Settings.Serialize(aWriter);
Expand All @@ -11,6 +13,8 @@ void AuthenticationResponse::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter
void AuthenticationResponse::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept
{
Type = static_cast<ResponseType>(Serialization::ReadVarInt(aReader) & 0xFFFFFFFF);
SKSEActive = Serialization::ReadBool(aReader);
MO2Active = Serialization::ReadBool(aReader);
Version = Serialization::ReadString(aReader);
UserMods.Deserialize(aReader);
Settings.Deserialize(aReader);
Expand Down
9 changes: 5 additions & 4 deletions Code/encoding/Messages/AuthenticationResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ struct AuthenticationResponse final : ServerMessage
{
kAccepted,
kWrongVersion,
kMissingMods,
kModsMismatch,
kClientModsDisallowed,
};

AuthenticationResponse() : ServerMessage(Opcode)
Expand All @@ -24,13 +25,13 @@ struct AuthenticationResponse final : ServerMessage

bool operator==(const AuthenticationResponse& achRhs) const noexcept
{
return GetOpcode() == achRhs.GetOpcode() &&
Type == achRhs.Type &&
UserMods == achRhs.UserMods &&
return GetOpcode() == achRhs.GetOpcode() && Type == achRhs.Type && UserMods == achRhs.UserMods &&
Settings == achRhs.Settings;
}

ResponseType Type;
bool SKSEActive{false};
bool MO2Active{false};
String Version;
Mods UserMods{};
ServerSettings Settings{};
Expand Down
80 changes: 63 additions & 17 deletions Code/server/GameServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <GameServer.h>
#include <Packet.hpp>


#include <Events/AdminPacketEvent.h>
#include <Events/CharacterRemoveEvent.h>
#include <Events/OwnershipTransferEvent.h>
Expand Down Expand Up @@ -38,27 +37,39 @@ Console::StringSetting sServerIconURL{"GameServer:sIconUrl", "URL to the image t
Console::StringSetting sTagList{"GameServer:sTagList", "List of tags, separated by a comma (,)", ""};
Console::StringSetting sAdminPassword{"GameServer:sAdminPassword", "Admin authentication password", ""};
Console::StringSetting sToken{"GameServer:sToken", "Admin token", ""};
Console::Setting bEnableMoPo{"ModPolicy:bEnabled", "Bypass the mod policy restrictions.", true,
Console::SettingsFlags::kHidden | Console::SettingsFlags::kLocked};
Console::Setting uDifficulty{"Gameplay:uDifficulty", "In game difficulty (0 to 5)", 4u};

// ModPolicy Stuff
Console::Setting bEnableMoPo{"ModPolicy:bDisableModCheck", "Bypass the checking of mods on the server", true,
Console::SettingsFlags::kHidden | Console::SettingsFlags::kLocked};
Console::Setting bDisallowSKSE{"ModPolicy:bDisallowSKSE", "Forbids joining of clients that have SKSE running", false,
Console::SettingsFlags::kHidden | Console::SettingsFlags::kLocked};
Console::Setting bDisallowMO2{"ModPolicy:bDisallowMO2", "Forbids joining of client that use Mod Organizer 2", false,
Console::SettingsFlags::kHidden | Console::SettingsFlags::kLocked};
// -- Commands --
Console::Command<bool> TogglePremium("TogglePremium", "Toggle the premium mode",
[](Console::ArgStack& aStack) { bPremiumTickrate = aStack.Pop<bool>(); });

Console::Command<> ShowVersion("version", "Show the version the server was compiled with",
[](Console::ArgStack&) { spdlog::get("ConOut")->info("Server " BUILD_COMMIT); });
Console::Command<> CrashServer("crash", "Crashes the server, don't use!", [](Console::ArgStack&) { int* i = 0; *i = 42; });
Console::Command<> ShowMoPoStatus("isMoPoActive", "Shows if the ModPolicy is active", [](Console::ArgStack&) {
spdlog::get("ConOut")->info("ModPolicy status: {}", bEnableMoPo ? "active" : "not active");
Console::Command<> CrashServer("crash", "Crashes the server, don't use!", [](Console::ArgStack&) {
int* i = 0;
*i = 42;
});
Console::Command<> ShowMoPoStatus("showMoPOStatus", "Shows the status of ModPolicy", [](Console::ArgStack&) {
auto formatStatus = [](bool aToggle) { return aToggle ? "yes" : "no"; };

spdlog::get("ConOut")->info("Modcheck: {}\nSKSE forbidden: {}\nMO2 forbidden: {}", formatStatus(bEnableMoPo),
formatStatus(bDisallowSKSE), formatStatus(bDisallowMO2));
});

// -- Constants --
constexpr char kBypassMoPoWarning[]{
"ModPolicy is disabled. This can lead to desync and other oddities. Make sure you know what you are doing. We "
"ModCheck is disabled. This can lead to desync and other oddities. Make sure you know what you are doing. We "
"may not be able to assist you if ModPolicy is disabled."};

constexpr char kMopoRecordsMissing[]{
"Failed to start: ModPolicy is enabled, but no mods are installed. Players wont be able "
"Failed to start: ModPolicy mod check is enabled, but no mods are installed. Players wont be able "
"to join! Please install Mods into the /data/ directory."};

} // namespace
Expand Down Expand Up @@ -252,7 +263,6 @@ void GameServer::OnUpdate()

auto& dispatcher = m_pWorld->GetDispatcher();


dispatcher.trigger(UpdateEvent{cDeltaSeconds});

if (m_requestStop)
Expand Down Expand Up @@ -445,6 +455,11 @@ static String PrettyPrintModList(const Vector<Mods::Entry>& acMods)
return text;
}

bool GameServer::ValidateAuthParams(ConnectionId_t aConnectionId, const UniquePtr<AuthenticationRequest>& acRequest)
{
return false;
}

void GameServer::HandleAuthenticationRequest(const ConnectionId_t aConnectionId,
const UniquePtr<AuthenticationRequest>& acRequest)
{
Expand All @@ -455,20 +470,54 @@ void GameServer::HandleAuthenticationRequest(const ConnectionId_t aConnectionId,

AuthenticationResponse serverResponse;
serverResponse.Version = BUILD_COMMIT;

using RT = AuthenticationResponse::ResponseType;
auto sendKick = [&](const RT type) {
serverResponse.Type = type;
Send(aConnectionId, serverResponse);
// the previous message is a lingering kick, it still gets delivered.
Kick(aConnectionId);
};
#if 1
// to make our testing life a bit easier.
if (acRequest->Version != BUILD_COMMIT)
{
spdlog::info("New player {:x} '{}' tried to connect with client {} - Version mismatch", aConnectionId,
remoteAddress, acRequest->Version.c_str());

serverResponse.Type = AuthenticationResponse::ResponseType::kWrongVersion;
Send(aConnectionId, serverResponse);
Kick(aConnectionId);
sendKick(RT::kWrongVersion);
return;
}
#endif

bool skseProblem = bDisallowSKSE && acRequest->SKSEActive;
bool mo2Problem = bDisallowMO2 && acRequest->MO2Active;

if (skseProblem || mo2Problem)
{
TiltedPhoques::String response;
if (skseProblem)
response += "SKSE ";
if (mo2Problem)
response += "MO2 ";

spdlog::info("New player {:x} '{}' tried to connect, but {}disallowed - Kicked.", aConnectionId, remoteAddress,
response.c_str());

serverResponse.SKSEActive = acRequest->SKSEActive;
serverResponse.MO2Active = acRequest->MO2Active;
sendKick(RT::kClientModsDisallowed);
return;
}

if (bDisallowMO2 && acRequest->MO2Active)
{
spdlog::info("New player {:x} '{}' tried to connect with ModOrganizer2 - MO2 is disallowed - Kicked.",
aConnectionId, remoteAddress);
serverResponse.MO2Active = true;
sendKick(RT::kClientModsDisallowed);
return;
}

// check if the proper server password was supplied.
if (acRequest->Token == sToken.value())
{
Expand Down Expand Up @@ -520,11 +569,8 @@ void GameServer::HandleAuthenticationRequest(const ConnectionId_t aConnectionId,
"ModPolicy: refusing connection {:x} because the following mods are installed on the client: {}",
aConnectionId, text.c_str());

serverResponse.Type = AuthenticationResponse::ResponseType::kMissingMods;
serverResponse.UserMods.ModList = std::move(modsToRemove.ModList);
Send(aConnectionId, serverResponse);
// This is a lingering kick, so sending the response should still succeed.
Kick(aConnectionId);
sendKick(RT::kModsMismatch);
return;
}
}
Expand Down
1 change: 1 addition & 0 deletions Code/server/GameServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ struct GameServer final : Server
}

protected:
bool ValidateAuthParams(ConnectionId_t aConnectionId, const UniquePtr<AuthenticationRequest>& acRequest);
void HandleAuthenticationRequest(ConnectionId_t aConnectionId, const UniquePtr<AuthenticationRequest>& acRequest);

// Implement TiltedPhoques::Server
Expand Down
2 changes: 1 addition & 1 deletion Code/server_runner/DediRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ void DediRunner::ReadStdin(uv_stream_t* apStream, ssize_t aRead, const uv_buf_t*

void DediRunner::AllocateBuffer(uv_handle_t* apHandle, size_t aSuggestedSize, uv_buf_t* apBuffer)
{
*apBuffer = uv_buf_init(static_cast<char*>(TiltedPhoques::Allocator::GetDefault()->Allocate(aSuggestedSize)), aSuggestedSize);
*apBuffer = uv_buf_init(static_cast<char*>(TiltedPhoques::Allocator::GetDefault()->Allocate(aSuggestedSize)), static_cast<uint32_t>(aSuggestedSize));
}

void DediRunner::PrintExecutorArrowHack()
Expand Down

0 comments on commit 6b33705

Please sign in to comment.