Is OnlineServices (aka OSSv2) compatible with Dedicated Servers?

So far I’m having a good experience with the plugins, however it doesn’t seem to support dedicated server. Reasons:

1- CreateSession requires LocalPlayer

On Sessions.h in OnlineServicesInterface) we have CreateSession, which needs this parameter:

/** Id handle for the local user which will perform the action */
FAccountId LocalAccountId;

This doesn’t seem possible to obtain without a LocalPlayer. I tried to pass it nullptr, but it fails. It does require the param.

For reference, Lyra’s code also doesn’t know what to do here.
On UCommonSessionSubsystem::CreateOnlineSessionInternalOSSv2 we find:

FCreateLobby::Params CreateParams;

if (LocalPlayer)
{
CreateParams.LocalAccountId = LocalPlayer->GetPreferredUniqueNetId().GetV2();
}
else if (bIsDedicatedServer)
{
// TODO what should this do for v2?
}

2- No space for multiple artifacts

Services uses a different way of writing artifacts on DefaultEngine.ini.

Subsystem (OSSv1) can be done on editor, you can add how many artifacts you want. On text you can write:

+Artifacts=(ArtifactName=“NAME”,ClientId=“CLIENT_ID”,ClientSecret=“CLIENT_SECRET”,ProductId=“PRODUCT_ID”,SandboxId=“SANDBOX_ID”,DeploymentId=“DEPLOYTMENT_ID”,ClientEncryptionKey=“CLIENT_ENCRYPTION_KEY”)

We can add how many lines we want. And we can reference the different artifacts.

Services (OSSv2) uses this on the file:

[OnlineServices.EOS]
ProductId=PRODUCT_ID
SandboxId=SANDBOX_ID
DeploymentId=DEPLOYTMENT_ID
ClientId=CLIENT_ID
ClientSecret=CLIENT_SECRET
ClientEncryptionKey=CLIENT_ENCRYPTION_KEY

There is only room for one.
The workaround has been to rewrite this whenever you want to build a server or a client. Which is impractical for quick iterations.

Has anyone managed to run a dedicated server and create sessions using OSSv2?

Posting for future surfers.

Whilst I do not have the dedicated servers setup yet, you can specify custom configs to be loaded in your build target, thus specifying the correct client id and secret for your client build and server build.

using UnrealBuildTool;
using System.Collections.Generic;

public class ProjectNameServerTarget: TargetRules
{
    public ProjectNameServerTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Server;
        DefaultBuildSettings = BuildSettingsVersion.V5;
        IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_5;
        ExtraModuleNames.Add("ProjectName");

        /*
        This tells UBT to load configuration files in the 
        Config/Custom/Server directory.
        See: https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-build-tool-target-reference?application_version=5.5
        */
        CustomConfig = "Server"; 
    }
}

You can see this used in the Lyra project to load custom config directory for the EOS platform build.
LyraGameEOS.Target.cs
Config/Custom/EOS/DefaultEngine.ini

I will update this further once I have a full solution for dedicated servers using OSSv2.

Ok so after digging around a bit in the OSSv2 Plugin, I found no way to use it for dedicated servers, as you said, it requires a local account ID.

I tried to just make a fake local account ID however an internal check fails so that didn’t work.

My solution has been to use the EOS SDK directly.

void CreateSessionWithEOSSDK(int32 MaxPlayers)
{
    // Get EOS Platform Handle from Online Services
    TSharedPtr<UE::Online::FOnlineServicesEOSGS> EOSServices = StaticCastSharedPtr<UE::Online::FOnlineServicesEOSGS>(OnlineServices);
    if (!EOSServices)
    {
        UE_LOG(LogOnline, Error, TEXT("Failed to cast Online Services to EOSGS"));
        OnSessionCreated.Broadcast(false, TEXT(""));
        return;
    }

    IEOSPlatformHandlePtr PlatformHandlePtr = EOSServices->GetEOSPlatformHandle();
    if (!PlatformHandlePtr)
    {
        UE_LOG(LogOnline, Error, TEXT("Failed to get EOS Platform Handle"));
        OnSessionCreated.Broadcast(false, TEXT(""));
        return;
    }

    EOS_HPlatform PlatformHandle = *PlatformHandlePtr;

    // Get Sessions Interface
    EOS_HSessions SessionsHandle = EOS_Platform_GetSessionsInterface(PlatformHandle);
    if (!SessionsHandle)
    {
        UE_LOG(LogOnline, Error, TEXT("Failed to get EOS Sessions Handle"));
        OnSessionCreated.Broadcast(false, TEXT(""));
        return;
    }

    // Create Session Modification
    EOS_Sessions_CreateSessionModificationOptions CreateOptions = {};
    CreateOptions.ApiVersion = EOS_SESSIONS_CREATESESSIONMODIFICATION_API_LATEST;
    CreateOptions.MaxPlayers = MaxPlayers;
    CreateOptions.SessionName = TCHAR_TO_UTF8(*FString::Printf(TEXT("DS_%s"), *FDateTime::Now().ToString()));
    CreateOptions.BucketId = "main";
    
    EOS_HSessionModification SessionModification = nullptr;
    EOS_EResult Result = EOS_Sessions_CreateSessionModification(SessionsHandle, &CreateOptions, &SessionModification);
    
    if (Result != EOS_EResult::EOS_Success)
    {
        UE_LOG(LogOnline, Error, TEXT("Failed to create session modification: %d"), static_cast<int32>(Result));
        OnSessionCreated.Broadcast(false, TEXT(""));
        return;
    }

    // Create the session
    EOS_Sessions_UpdateSessionOptions UpdateOptions = {};
    UpdateOptions.ApiVersion = EOS_SESSIONS_UPDATESESSION_API_LATEST;
    UpdateOptions.SessionModificationHandle = SessionModification;

    // Simple callback using a lambda
    EOS_Sessions_UpdateSession(SessionsHandle, &UpdateOptions,
        this,
        [](const EOS_Sessions_UpdateSessionCallbackInfo* Data) -> void {
            if (auto* ThisPtr = static_cast<ThisClass*>(Data->ClientData))
            {
                FString SessionId = UTF8_TO_TCHAR(Data->SessionId);
                
                AsyncTask(ENamedThreads::GameThread, [ThisPtr, Result = Data->ResultCode, SessionId]() {
                    if (Result == EOS_EResult::EOS_Success)
                    {
                        UE_LOG(LogOnline, Log, TEXT("EOS Session created successfully. Session ID: %s"), *SessionId);
                        ThisPtr->OnSessionCreated.Broadcast(true, SessionId);
                    }
                    else
                    {
                        UE_LOG(LogOnline, Error, TEXT("EOS Session creation failed: %d"), static_cast<int32>(Result));
                        ThisPtr->OnSessionCreated.Broadcast(false, TEXT(""));
                    }
                });
            }
        });

    // Release the modification handle
    EOS_SessionModification_Release(SessionModification);
}

Headers used:

#include "eos_sdk.h"
#include "eos_sessions.h"
#include "Online/OnlineServices.h"
#include "Online/OnlineServicesEOSGS.h"

Note: This code is a quick draft rewrite to remove my project specific functionality so it may not work OOTB.
I have also not tested the wider use of this implementation with OSSv2 so am unsure whether you can then use this with the Sessions Interface directly (I assume it should mostly work fine, just not sure considering creating a session requires a local user, if things like finding a session also assume an owning account exists.)

Again, I will continue to post my findings under this thread for future reference.