#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include 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().connect<&TransportService::HandleUpdate>(this); m_settingsChangeConnection = m_dispatcher.sink().connect<&TransportService::HandleNotifySettingsChange>(this); m_connectedConnection = m_dispatcher.sink().connect<&TransportService::HandleConnected>(this); m_disconnectedConnection = m_dispatcher.sink().connect<&TransportService::HandleDisconnected>(this); m_connected = false; auto handlerGenerator = [this](auto& x) { using T = typename std::remove_reference_t::Type; m_messageHandlers[T::Opcode] = [this](UniquePtr& apMessage) { const auto pRealMessage = TiltedPhoques::CastUnique(std::move(apMessage)); m_dispatcher.trigger(*pRealMessage); }; return false; }; ServerMessageFactory::Visit(handlerGenerator); // Override authentication response m_messageHandlers[AuthenticationResponse::Opcode] = [this](UniquePtr& apMessage) { const auto pRealMessage = TiltedPhoques::CastUnique(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(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().GetUser().id; auto* pNpc = Cast(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); }