Skip to content

Commit

Permalink
Add companion mode: CLIFp piggybacks off the Launcher's services
Browse files Browse the repository at this point in the history
  • Loading branch information
oblivioncth committed Nov 23, 2023
1 parent 8672255 commit 87a3394
Show file tree
Hide file tree
Showing 22 changed files with 270 additions and 616 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ project(CLIFp

# Get helper scripts
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchOBCMake.cmake)
fetch_ob_cmake("v0.3.3")
fetch_ob_cmake("928cbafc2036f97cfaeb50cd892209b6e1037b6d")

# Initialize project according to standard rules
include(OB/Project)
Expand Down Expand Up @@ -72,7 +72,7 @@ endif()

include(OB/FetchQx)
ob_fetch_qx(
REF "2bbf83e59b0aadc3891440193892be1a2a19c00e"
REF "a31cbbf994d3709575cb3a54a0b2b6e8863f4089"
COMPONENTS
${CLIFP_QX_COMPONENTS}
)
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ If the application needs to use files from a Data Pack that pack will need to be
The applications and arguments that are used for each game/animation can be found within the Flashpoint database ([FP Install Dir]\Data\flashpoint.sqlite)

### Flashpoint Protocol

CLIFp supports the "flashpoint" protocol, which means it can launch titles through URL with a custom scheme, followed by a title's UUID, like this:

flashpoint://37e5c215-9c39-4a3d-9912-b4343a17027e
Expand All @@ -135,6 +136,12 @@ If for whatever reason the service through which you wish to share a link does n
> [!IMPORTANT]
> You will want to disable the "Register As Protocol Handler" option in the default launcher or else it will replace CLIFp as the "flashpoint" protocol handler every time it's started.
### Companion Mode

It is recommended to only use CLIFp when the regular launcher isn't running as it allows fully independent operation since it can start and stop required services on its own; however, CLIFp can be started while the standard launcher is running, in which case it will run in "Companion Mode" and utilize the launcher's services instead.

The catch with this mode is that CLIFp will be required to shutdown if at any point the standard launcher is closed.

## All Commands/Options

Most options have short and long forms, which are interchangeable. For options that take a value, a space or **=** can be used between the option and its value, i.e.
Expand Down
5 changes: 0 additions & 5 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ set(CLIFP_SOURCE
tools/mounter_qmp.cpp
tools/mounter_router.h
tools/mounter_router.cpp
tools/processbider_p.h
tools/processbider.h
tools/processbider.cpp
frontend/message.h
frontend/statusrelay.h
frontend/statusrelay.cpp
Expand Down Expand Up @@ -93,7 +90,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL Windows)
task/t-exec_win.cpp
task/t-bideprocess.h
task/t-bideprocess.cpp
tools/processbider_p_win.cpp
)

list(APPEND CLIFP_LINKS
Expand All @@ -108,7 +104,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux)
task/t-awaitdocker.h
task/t-awaitdocker.cpp
task/t-exec_linux.cpp
tools/processbider_p_linux.cpp
)
list(APPEND CLIFP_LINKS
PRIVATE
Expand Down
3 changes: 3 additions & 0 deletions app/src/command/c-play.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,6 @@ Qx::Error CPlay::perform()
// Handle entry
return std::visit([this](auto arg) { return this->handleEntry(arg); }, entry);
}

//Public:
bool CPlay::requiresServices() const { return true; }
3 changes: 3 additions & 0 deletions app/src/command/c-play.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ class CPlay : public TitleCommand
QList<const QCommandLineOption*> options() override;
QString name() override;
Qx::Error perform() override;

public:
bool requiresServices() const override;
};
REGISTER_COMMAND(CPlay::NAME, CPlay, CPlay::DESCRIPTION);

Expand Down
3 changes: 3 additions & 0 deletions app/src/command/c-prepare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ Qx::Error CPrepare::perform()
// Return success
return Qx::Error();
}

//Public:
bool CPrepare::requiresServices() const { return true; }
3 changes: 3 additions & 0 deletions app/src/command/c-prepare.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class CPrepare : public TitleCommand
QList<const QCommandLineOption*> options() override;
QString name() override;
Qx::Error perform() override;

public:
bool requiresServices() const override;
};
REGISTER_COMMAND(CPrepare::NAME, CPrepare, CPrepare::DESCRIPTION);

Expand Down
3 changes: 3 additions & 0 deletions app/src/command/c-run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ Qx::Error CRun::perform()
// Return success
return CRunError();
}

//Public:
bool CRun::requiresServices() const { return true; }
3 changes: 3 additions & 0 deletions app/src/command/c-run.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class CRun : public Command
QSet<const QCommandLineOption*> requiredOptions() override;
QString name() override;
Qx::Error perform() override;

public:
bool requiresServices() const override;
};
REGISTER_COMMAND(CRun::NAME, CRun, CRun::DESCRIPTION);

Expand Down
1 change: 1 addition & 0 deletions app/src/command/command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ QSet<const QCommandLineOption*> Command::requiredOptions() { return {}; }

//Public:
bool Command::requiresFlashpoint() const { return true; }
bool Command::requiresServices() const { return false; }
bool Command::autoBlockNewInstances() const { return true; }

Qx::Error Command::process(const QStringList& commandLine)
Expand Down
1 change: 1 addition & 0 deletions app/src/command/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class Command

public:
virtual bool requiresFlashpoint() const;
virtual bool requiresServices() const;
virtual bool autoBlockNewInstances() const;
Qx::Error process(const QStringList& commandLine);
};
Expand Down
116 changes: 81 additions & 35 deletions app/src/kernel/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ Core::Core(QObject* parent) :
QObject(parent),
mCriticalErrorOccurred(false),
mStatusHeading(u"Initializing"_s),
mStatusMessage(u"..."_s)
mStatusMessage(u"..."_s),
mServicesMode(ServicesMode::Standalone)
{
establishCanonCore(*this); // Ignore return value as there should never be more than one Core with current design
}
Expand Down Expand Up @@ -342,50 +343,81 @@ Qx::Error Core::initialize(QStringList& commandLine)
logEvent(NAME, LOG_EVENT_GLOBAL_OPT.arg(globalOptions));

// Check for valid arguments
if(validArgs)
if(!validArgs)
{
// Handle each global option
mNotificationVerbosity = clParser.isSet(CL_OPTION_SILENT) ? NotificationVerbosity::Silent :
commandLine.clear(); // Clear remaining options since they are now irrelevant
showHelp();

CoreError err(CoreError::InvalidOptions, clParser.errorText());
postError(NAME, err);
return err;
}

// Handle each global option
mNotificationVerbosity = clParser.isSet(CL_OPTION_SILENT) ? NotificationVerbosity::Silent :
clParser.isSet(CL_OPTION_QUIET) ? NotificationVerbosity::Quiet : NotificationVerbosity::Full;
logEvent(NAME, LOG_EVENT_NOTIFCATION_LEVEL.arg(ENUM_NAME(mNotificationVerbosity)));
logEvent(NAME, LOG_EVENT_NOTIFCATION_LEVEL.arg(ENUM_NAME(mNotificationVerbosity)));

if(clParser.isSet(CL_OPTION_VERSION))
{
showVersion();
commandLine.clear(); // Clear args so application terminates after Core setup
logEvent(NAME, LOG_EVENT_VER_SHOWN);
}
else if(clParser.isSet(CL_OPTION_HELP) || (!isActionableOptionSet(clParser) && clParser.positionalArguments().count() == 0)) // Also when no parameters
if(clParser.isSet(CL_OPTION_VERSION))
{
showVersion();
commandLine.clear(); // Clear args so application terminates after Core setup
logEvent(NAME, LOG_EVENT_VER_SHOWN);
}
else if(clParser.isSet(CL_OPTION_HELP) || (!isActionableOptionSet(clParser) && clParser.positionalArguments().count() == 0)) // Also when no parameters
{
showHelp();
commandLine.clear(); // Clear args so application terminates after Core setup
logEvent(NAME, LOG_EVENT_G_HELP_SHOWN);
}
else
{
QStringList pArgs = clParser.positionalArguments();
if(pArgs.count() == 1 && pArgs.front().startsWith(FLASHPOINT_PROTOCOL_SCHEME))
{
showHelp();
commandLine.clear(); // Clear args so application terminates after Core setup
logEvent(NAME, LOG_EVENT_G_HELP_SHOWN);
logEvent(NAME, LOG_EVENT_PROTOCOL_FORWARD);
commandLine = {"play", "-u", pArgs.front()};
}
else
{
QStringList pArgs = clParser.positionalArguments();
if(pArgs.count() == 1 && pArgs.front().startsWith(FLASHPOINT_PROTOCOL_SCHEME))
{
logEvent(NAME, LOG_EVENT_PROTOCOL_FORWARD);
commandLine = {"play", "-u", pArgs.front()};
}
else
commandLine = pArgs; // Remove core options from command line list
}

// Return success
return CoreError();
commandLine = pArgs; // Remove core options from command line list
}
else
{
commandLine.clear(); // Clear remaining options since they are now irrelevant
showHelp();

CoreError err(CoreError::InvalidOptions, clParser.errorText());
// Return success
return CoreError();
}

void Core::setServicesMode(ServicesMode mode)
{
logEvent(NAME, LOG_EVENT_MODE_SET.arg(ENUM_NAME(mode)));
mServicesMode = mode;

if(mode == ServicesMode::Companion)
watchLauncher();
}

void Core::watchLauncher()
{
logEvent(NAME, LOG_EVENT_LAUNCHER_WATCH);

using namespace std::chrono_literals;
mLauncherWatcher.setProcessName(Fp::Install::LAUNCHER_NAME);
#ifdef __linux__
mLauncherWatcher.setPollRate(1s); // Generous rate since while we need to know quickly, we don't THAT quickly
#endif
connect(&mLauncherWatcher, &Qx::ProcessBider::established, this, [this]{
logEvent(NAME, LOG_EVENT_LAUNCHER_WATCH_HOOKED);
});
connect(&mLauncherWatcher, &Qx::ProcessBider::errorOccurred, this, [this](Qx::ProcessBiderError err){
logError(NAME, err);
});
connect(&mLauncherWatcher, &Qx::ProcessBider::finished, this, [this]{
// Launcher closed (or can't be hooked), need to bail
CoreError err(CoreError::CompanionModeLauncherClose, LOG_EVENT_LAUNCHER_CLOSED_RESULT);
postError(NAME, err);
return err;
}
emit abort(err);
});

mLauncherWatcher.start();
}

void Core::attachFlashpoint(std::unique_ptr<Fp::Install> flashpointInstall)
Expand Down Expand Up @@ -491,6 +523,13 @@ bool Core::blockNewInstances()
CoreError Core::enqueueStartupTasks(const QString& serverOverride)
{
logEvent(NAME, LOG_EVENT_ENQ_START);

if(mServicesMode == ServicesMode::Companion)
{
logEvent(NAME, LOG_EVENT_SERVICES_FROM_LAUNCHER);
// TODO: Allegedly apache and php are going away at some point so this hopefully isn't needed for long
return !serverOverride.isEmpty() ? CoreError(CoreError::CompanionModeServerOverride) : CoreError();
}

#ifdef __linux__
/* On Linux X11 Server needs to be temporarily be set to allow connections from root for docker,
Expand Down Expand Up @@ -607,6 +646,13 @@ CoreError Core::enqueueStartupTasks(const QString& serverOverride)
void Core::enqueueShutdownTasks()
{
logEvent(NAME, LOG_EVENT_ENQ_STOP);

if(mServicesMode == ServicesMode::Companion)
{
logEvent(NAME, LOG_EVENT_SERVICES_FROM_LAUNCHER);
return;
}

// Add Stop entries from services
for(const Fp::StartStop& stopEntry : qxAsConst(mFlashpointInstall->services().stop))
{
Expand Down
Loading

0 comments on commit 87a3394

Please sign in to comment.