Live link face app /Transport

If anybody ends up curious about this, I was able to reverse-engineer it.

/Transport command is for downloading a single file over a TCP connection. You can use the returned .mov file from /RecordStopConfirm as the input for /Transport.

Before transporting you have to spin up a TCP listener and handle the response data. The format is fileSize:int32, data:tarray{byte}

If you want all of the files, you’ll need to look at what files that the Capture Manager window normally inputs, and request each of these 1 by 1.

class FLiveLinkTcpListener : public FRunnable
{
public:
	FLiveLinkTcpListener(int32 InPort);
	virtual ~FLiveLinkTcpListener();

	void SetFileName(const FString& InFileName);

	// FRunnable interface
	virtual bool Init() override;
	virtual uint32 Run() override;
	virtual void Stop() override;

	DECLARE_DELEGATE_OneParam(FOnFileTransferComplete, const FString& /*FileName*/);
	FOnFileTransferComplete OnFileTransferComplete;

private:
	void HandleLiveLinkData(FSocket* ConnectionSocket);
	const FIPv4Endpoint& GetLocalEndpoint() const;
	FSocket* GetSocket() const;
	bool IsActive() const;
	FOnTcpListenerConnectionAccepted& OnConnectionAccepted();

	bool bDeleteSocket = false;
	FIPv4Endpoint Endpoint;
	FTimespan SleepTime;
	FSocket* Socket = nullptr;
	bool bStopping = false;
	bool bSocketReusable = true;
	FRunnableThread* Thread = nullptr;
	FString FileName;

	DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnTcpListenerConnectionAccepted, FSocket*, const FIPv4Endpoint&)
	FOnTcpListenerConnectionAccepted ConnectionAcceptedDelegate;
};

#include "CaptureOrchestrator/LiveLinkTcpListener.h"

#include "HAL/RunnableThread.h"
#include "Async/Async.h"

FLiveLinkTcpListener::FLiveLinkTcpListener(int32 InPort)
{
    FIPv4Address Addr;
    FIPv4Address::Parse(TEXT("0.0.0.0"), Addr);
    Endpoint = FIPv4Endpoint(Addr, InPort);

    Thread = FRunnableThread::Create(this, TEXT("FLiveLinkTcpListener"), 8 * 1024, TPri_Normal);
}

FLiveLinkTcpListener::~FLiveLinkTcpListener()
{
    Stop();
    if (Thread != nullptr)
    {
        Thread->Kill(true);
        delete Thread;
    }
}

void FLiveLinkTcpListener::SetFileName(const FString& InFileName)
{
    FileName = InFileName;
}

bool FLiveLinkTcpListener::Init()
{
    if (Socket == nullptr)
    {
        Socket = FTcpSocketBuilder(TEXT("FTcpListener server"))
            .AsReusable(bSocketReusable)
            .BoundToEndpoint(Endpoint)
            .Listening(8)
            .WithSendBufferSize(2 * 1024 * 1024);
    }

    return (Socket != nullptr);
}

uint32 FLiveLinkTcpListener::Run()
{
    TSharedRef<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();

    while (!bStopping)
    {
        bool Pending = false;
        if (Socket->WaitForPendingConnection(Pending, SleepTime) && Pending)
        {
            FSocket* ConnectionSocket = Socket->Accept(*RemoteAddress, TEXT("FTcpListener client"));
            if (ConnectionSocket != nullptr)
            {
                // New method for processing incoming data from Live Link
                HandleLiveLinkData(ConnectionSocket);
            }
        }
        else if (!Pending && SleepTime == FTimespan::Zero())
        {
            FPlatformProcess::Sleep(0.f);
        }
        else
        {
            FPlatformProcess::Sleep(SleepTime.GetSeconds());
        }
    }

    return 0;
}

void FLiveLinkTcpListener::Stop()
{
    bStopping = true;
}

void FLiveLinkTcpListener::HandleLiveLinkData(FSocket* ConnectionSocket)
{
    if (!ensure(!FileName.IsEmpty()))
    {
        return;
    }

    FString BaseDir = FPaths::ProjectContentDir() / TEXT("Temp") / FPaths::GetPath(FileName);
    IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

    PlatformFile.CreateDirectoryTree(*BaseDir);
    FString FilePath = BaseDir / FPaths::GetCleanFilename(FileName);
    IFileHandle* FileHandle = PlatformFile.OpenWrite(*FilePath);

    if (!FileHandle)
    {
        return;
    }

    int32 TotalBytesRead = 0;
    int32 BytesRead = 0;
    int32 FileSize = 0;

    ConnectionSocket->Recv((uint8*)&FileSize, sizeof(int32), BytesRead);
    FileSize = ByteSwap(FileSize);

    if (FileSize > 0)
    {
        TArray<uint8> DataBuffer;
        const int32 ChunkSize = 8192;
        DataBuffer.SetNumUninitialized(ChunkSize);

        while (TotalBytesRead < FileSize)
        {
            int32 Remaining = FMath::Min(ChunkSize, FileSize - TotalBytesRead);
            if (ConnectionSocket->Recv(DataBuffer.GetData(), Remaining, BytesRead))
            {
                FileHandle->Write(DataBuffer.GetData(), BytesRead);
                TotalBytesRead += BytesRead;
            }
            else
            {
                break;
            }
        }
    }

    delete FileHandle;

    if (OnFileTransferComplete.IsBound())
    {
        OnFileTransferComplete.Execute(FileName);
    }
}

const FIPv4Endpoint& FLiveLinkTcpListener::GetLocalEndpoint() const
{
    return Endpoint;
}

FSocket* FLiveLinkTcpListener::GetSocket() const
{
    return Socket;
}

bool FLiveLinkTcpListener::IsActive() const
{
    return ((Socket != nullptr) && !bStopping);
}

FOnTcpListenerConnectionAccepted& FLiveLinkTcpListener::OnConnectionAccepted()
{
    return ConnectionAcceptedDelegate;
}
1 Like