F4MP/codigos originales/tiltedcode/Code/client/Services/Generic/TransportService.cpp
2026-01-06 18:53:59 +01:00

279 lines
7.7 KiB
C++

#include <Services/TransportService.h>
#include <Events/ConnectedEvent.h>
#include <Events/ConnectionErrorEvent.h>
#include <Events/DisconnectedEvent.h>
#include <Events/UpdateEvent.h>
#include <Games/References.h>
#include <Games/TES.h>
#include <Forms/TESWorldSpace.h>
#include <Forms/TESObjectCELL.h>
#include <TimeManager.h>
#include <Forms/TESNPC.h>
#include <TiltedOnlinePCH.h>
#include <World.h>
#include <Messages/AuthenticationRequest.h>
#include <Messages/ServerMessageFactory.h>
#include <Messages/NotifySettingsChange.h>
#include <Packet.hpp>
#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
: m_world(aWorld)
, m_dispatcher(aDispatcher)
{
m_updateConnection = m_dispatcher.sink<UpdateEvent>().connect<&TransportService::HandleUpdate>(this);
m_settingsChangeConnection = m_dispatcher.sink<NotifySettingsChange>().connect<&TransportService::HandleNotifySettingsChange>(this);
m_connectedConnection = m_dispatcher.sink<ConnectedEvent>().connect<&TransportService::HandleConnected>(this);
m_disconnectedConnection = m_dispatcher.sink<DisconnectedEvent>().connect<&TransportService::HandleDisconnected>(this);
m_connected = false;
auto handlerGenerator = [this](auto& x)
{
using T = typename std::remove_reference_t<decltype(x)>::Type;
m_messageHandlers[T::Opcode] = [this](UniquePtr<ServerMessage>& apMessage)
{
const auto pRealMessage = TiltedPhoques::CastUnique<T>(std::move(apMessage));
m_dispatcher.trigger(*pRealMessage);
};
return false;
};
ServerMessageFactory::Visit(handlerGenerator);
// Override authentication response
m_messageHandlers[AuthenticationResponse::Opcode] = [this](UniquePtr<ServerMessage>& apMessage)
{
const auto pRealMessage = TiltedPhoques::CastUnique<AuthenticationResponse>(std::move(apMessage));
HandleAuthenticationResponse(*pRealMessage);
};
}
bool TransportService::Send(const ClientMessage& acMessage) const noexcept
{
static thread_local ScratchAllocator s_allocator(1 << 18);
struct ScopedReset
{
~ScopedReset() { s_allocator.Reset(); }
} allocatorGuard;
if (IsConnected())
{
ScopedAllocator _{s_allocator};
Buffer buffer(1 << 16);
Buffer::Writer writer(&buffer);
writer.WriteBits(0, 8); // Write first byte as packet needs it
acMessage.Serialize(writer);
TiltedPhoques::PacketView packet(reinterpret_cast<char*>(buffer.GetWriteData()), writer.Size());
Client::Send(&packet);
return true;
}
return false;
}
void TransportService::OnConsume(const void* apData, uint32_t aSize)
{
ServerMessageFactory factory;
TiltedPhoques::ViewBuffer buf((uint8_t*)apData, aSize);
Buffer::Reader reader(&buf);
auto pMessage = factory.Extract(reader);
if (!pMessage)
{
spdlog::error("Couldn't parse packet from server");
return;
}
m_messageHandlers[pMessage->GetOpcode()](pMessage);
}
void TransportService::OnConnected()
{
AuthenticationRequest request{};
request.Version = BUILD_COMMIT;
request.SKSEActive = IsScriptExtenderLoaded();
request.MO2Active = GetModuleHandleW(kMO2DllName);
request.Token = m_serverPassword;
m_serverPassword = "";
PlayerCharacter* pPlayer = PlayerCharacter::Get();
// 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>(pPlayer->baseForm);
if (pNpc)
{
request.Username = pNpc->fullName.value.AsAscii();
}
else
{
request.Username = "Some dragon boi";
}
auto* const cpModManager = ModManager::Get();
for (auto* pMod : cpModManager->mods)
{
if (!pMod->IsLoaded())
continue;
auto& entry = request.UserMods.ModList.emplace_back();
entry.Id = pMod->GetId();
entry.IsLite = pMod->IsLite();
entry.Filename = pMod->filename;
}
auto& modSystem = m_world.GetModSystem();
if (pPlayer->GetWorldSpace())
modSystem.GetServerModId(pPlayer->GetWorldSpace()->formID, request.WorldSpaceId);
modSystem.GetServerModId(pPlayer->parentCell->formID, request.CellId);
request.Level = pPlayer->GetLevel();
auto* pGameTime = TimeData::Get();
request.PlayerTime.TimeScale = pGameTime->TimeScale->f;
request.PlayerTime.Time = pGameTime->GameHour->f;
request.PlayerTime.Year = pGameTime->GameYear->f;
request.PlayerTime.Month = pGameTime->GameMonth->f;
request.PlayerTime.Day = pGameTime->GameDay->f;
Send(request);
}
void TransportService::OnDisconnected(EDisconnectReason aReason)
{
m_connected = false;
spdlog::warn("Disconnected from server {}", aReason);
m_dispatcher.trigger(DisconnectedEvent());
}
void TransportService::OnUpdate()
{
}
void TransportService::HandleUpdate(const UpdateEvent& acEvent) noexcept
{
Update();
}
void TransportService::HandleConnected(const ConnectedEvent& acEvent) noexcept
{
m_localPlayerId = acEvent.PlayerId;
}
void TransportService::HandleDisconnected(const DisconnectedEvent& acEvent) noexcept
{
m_localPlayerId = NULL;
}
void TransportService::HandleAuthenticationResponse(const AuthenticationResponse& acMessage) noexcept
{
using AR = AuthenticationResponse::ResponseType;
if (acMessage.Type == AR::kAccepted)
{
m_connected = true;
m_world.SetServerSettings(acMessage.Settings);
m_dispatcher.trigger(acMessage.UserMods);
m_dispatcher.trigger(acMessage.Settings);
m_dispatcher.trigger(ConnectedEvent(acMessage.PlayerId));
return; // quit the function here.
}
// error finding
TiltedPhoques::String ErrorInfo;
ErrorInfo = "{";
switch (acMessage.Type)
{
case AR::kWrongVersion:
ErrorInfo += "\"error\": \"wrong_version\", \"data\": {";
ErrorInfo += fmt::format("\"expectedVersion\": \"{}\", \"version\": \"{}\"", acMessage.Version, BUILD_COMMIT);
ErrorInfo += "}";
break;
case AR::kModsMismatch:
{
ErrorInfo += "\"error\": \"mods_mismatch\", \"data\": {\"mods\": [";
bool first = true;
for (const auto& m : acMessage.UserMods.ModList)
{
if (!first)
ErrorInfo += ",";
ErrorInfo += fmt::format("[\"{}\",\"{}\"]", m.Filename.c_str(), m.Id);
first = false;
}
ErrorInfo += "]}";
break;
}
case AR::kClientModsDisallowed:
{
ErrorInfo += "\"error\": \"client_mods_disallowed\", \"data\": { \"mods\": [";
if (acMessage.SKSEActive)
ErrorInfo += "\"SKSE\"";
if (acMessage.MO2Active)
if (acMessage.SKSEActive)
ErrorInfo += ",";
ErrorInfo += "\"MO2\"";
ErrorInfo += "]}";
break;
}
case AR::kWrongPassword:
{
ErrorInfo += "\"error\": \"wrong_password\"";
break;
}
case AR::kServerFull:
{
ErrorInfo += "\"error\": \"server_full\"";
break;
}
default: ErrorInfo += "\"error\": \"no_reason\""; break;
}
ErrorInfo += "}";
ConnectionErrorEvent errorEvent;
if (!ErrorInfo.empty())
{
spdlog::error(ErrorInfo.c_str());
errorEvent.ErrorDetail = std::move(ErrorInfo);
}
m_dispatcher.trigger(errorEvent);
}
void TransportService::HandleNotifySettingsChange(const NotifySettingsChange& acMessage) noexcept
{
m_world.SetServerSettings(acMessage.Settings);
m_dispatcher.trigger(acMessage.Settings);
}