Create a new local player/ new player controller on unassigned gamepad button press

So I found a workaround that is kinda ugly and invasive (in my opinion) but it works :

You should create a custom engine subsystem, who’s single purpose is to send an event on key press (you can add this feature on any subsystem really, in my case I didn’t have one so I created it). This event is fired when an input preprocessor receives a key down.
Relevant engine files :
Engine\Source\Runtime\Engine\Classes\GameFramework\InputDeviceSubsystem.h
Engine\Source\Runtime\Engine\Private\GameFramework\InputDeviceSubsystem.cpp
Engine\Source\Runtime\Slate\Public\Framework\Application\IInputProcessor.h

Example :
YourSubsystem.h

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "YourSubsystem.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnyKeyPressSignature, FKeyEvent, KeyEvent);

class FYourSubsystemInputProcessor;

/**
 * 
 */
UCLASS()
class UYourSubsystem : public UEngineSubsystem
{
	GENERATED_BODY()

public:
	static UYourSubsystem* Get();

	UFUNCTION(BlueprintCallable, BlueprintPure)
	static FInputDeviceId GetInputDeviceIdFromKeyEvent(FKeyEvent KeyEvent);
	
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

	UPROPERTY(BlueprintAssignable)
	FOnAnyKeyPressSignature OnAnyKeyPressDelegate;

private:
	friend class FYourSubsystemInputProcessor;
	
	void PreprocessorReceivedInput(const FKeyEvent& KeyEvent);

	TSharedPtr<FYourSubsystemInputProcessor> InputProcessor;
};

YourSubsystem.cpp

#include "YourSubsystem.h"

#include "Framework/Application/IInputProcessor.h"

class FYourSubsystemInputProcessor : public IInputProcessor
{
	friend class FYourSubsystem;

public:

	virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override
	{
		
	}
	
	void NotifySubsystemOfKeyPress(const FKeyEvent& KeyEvent)
	{
		if (UYourSubsystem* Subsystem = UYourSubsystem::Get())
		{
			Subsystem->PreprocessorReceivedInput(KeyEvent);
		}
	}
	
	virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InEvent) override
	{
		NotifySubsystemOfKeyPress(InEvent);
		return false;
	}
};

UYourSubsystem* UYourSubsystem::Get()
{
	return GEngine ? GEngine->GetEngineSubsystem<UYourSubsystem>() : nullptr;
}

FInputDeviceId UXpinEngineSubsystem::GetInputDeviceIdFromKeyEvent(FKeyEvent KeyEvent)
{
	return KeyEvent.GetInputDeviceId();
}

void UYourSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// We have to have a valid slate app to run this subsystem
	check(FSlateApplication::IsInitialized());
	
	InputProcessor = MakeShared<FYourSubsystemInputProcessor>();
	FSlateApplication::Get().RegisterInputPreProcessor(InputProcessor);
}

void UYourSubsystem::Deinitialize()
{
	Super::Deinitialize();
	
	if (FSlateApplication::IsInitialized())
	{
		FSlateApplication::Get().UnregisterInputPreProcessor(InputProcessor);
	}
	InputProcessor.Reset();
}

void UYourSubsystem::PreprocessorReceivedInput(const FKeyEvent& KeyEvent)
{
	OnAnyKeyPressDelegate.Broadcast(KeyEvent);
}

You can then use this event that is fired on EVERY KEY PRESS and check if the controller id stored in KeyEvent has a player. If not, create it.

I also added a little helper blueprint function that returns the Input device id since the function is not exposed in blueprints natively.