Skip to content

Commit e529222

Browse files
authored
Merge pull request #104 from oblivioncth/dev
Merge to master for v0.9.13.1
2 parents 907384b + e37014b commit e529222

File tree

6 files changed

+149
-37
lines changed

6 files changed

+149
-37
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.24.0...3.26.0)
66
# Project
77
# NOTE: DON'T USE TRAILING ZEROS IN VERSIONS
88
project(CLIFp
9-
VERSION 0.9.13
9+
VERSION 0.9.13.1
1010
LANGUAGES CXX
1111
DESCRIPTION "Command-line Interface for Flashpoint Archive"
1212
)
@@ -84,7 +84,7 @@ ob_fetch_qx(
8484

8585
# Fetch libfp (build and import from source)
8686
include(OB/Fetchlibfp)
87-
ob_fetch_libfp("v0.5.5")
87+
ob_fetch_libfp("v0.5.5.1")
8888

8989
# Fetch QI-QMP (build and import from source)
9090
include(OB/FetchQI-QMP)

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ Title Comamnd Shared Options:
176176
- **-S | --subtitle-strict:** Same as **-s**, but only exact matches are considered
177177
- **-r | --random:** Selects a random title from the database. Must be followed by a library filter: `all`/`any`, `game`/`arcade`, or `animation`/`theatre`
178178

179-
The **-title** and **-subtitle** options are case-insensitive and will match any title that contains the value provided; however, the provided title should match as closely as possible to how it appears within Flashpoint, as checks for close matches are limited due to technical restrictions. If more than one entry is found, a dialog window with more information will be displayed so that the intended title can be selected, though there is a limit to the number of matches.
179+
The **--title** and **--subtitle** options are case-insensitive and will match any title that contains the value provided; however, the provided title should match as closely as possible to how it appears within Flashpoint, as checks for close matches are limited due to technical restrictions. If more than one entry is found, a dialog window with more information will be displayed so that the intended title can be selected, though there is a limit to the number of matches.
180180

181-
The **-title-strict** and **-subtitle-strict** options only consider exact matches and are performed slightly faster than their more flexible counterparts.
181+
The **--title-strict** and **--subtitle-strict** options only consider exact matches and are performed slightly faster than their more flexible counterparts.
182182

183183
Tip: You can use **-subtitle** with an empty string (i.e. `-s ""`) to see all of the additional-apps for a given title.
184184

@@ -208,6 +208,8 @@ Notes:
208208
Options:
209209

210210
- [Title Command](#title-commands) options
211+
- **--ruffle:** Forces the use of Ruffle for applicable Flash games. Takes precedence over **--flash**.
212+
- **--flash:** Forces the use of the standard app (usually Flash Player) for applicable Flash games.
211213

212214
Notes:
213215
- You can use `--` to pass arguments directly to the title's underlying executable/script, which can be useful for testing or customizing your experience:

lib/backend/src/command/c-play.cpp

Lines changed: 117 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ void CPlay::addPassthroughParameters(QString& param)
8282
}
8383
}
8484

85+
void CPlay::addPassthroughParameters(QStringList& param)
86+
{
87+
// See above method
88+
QStringList ptp = mParser.positionalArguments();
89+
param.append(ptp);
90+
}
91+
8592
QString CPlay::getServerOverride(const Fp::GameData& gd)
8693
{
8794
QString override = gd.isNull() ? QString() : gd.parameters().server();
@@ -91,6 +98,36 @@ QString CPlay::getServerOverride(const Fp::GameData& gd)
9198
return override;
9299
}
93100

101+
bool CPlay::useRuffle(const Fp::Game& game, Task::Stage stage)
102+
{
103+
if(game.platformName() != u"Flash"_s || stage != Task::Stage::Primary)
104+
return false;
105+
106+
auto& extCfg = mCore.fpInstall().extConfig();
107+
if(mParser.isSet(CL_OPTION_RUFFLE))
108+
{
109+
logEvent(LOG_EVENT_FORCING_RUFFLE);
110+
return true;
111+
}
112+
else if(mParser.isSet(CL_OPTION_FLASH))
113+
{
114+
logEvent(LOG_EVENT_FORCING_FLASH);
115+
return false;
116+
}
117+
else if(extCfg.com_ruffle_enabled && !game.ruffleSupport().isEmpty())
118+
{
119+
logEvent(LOG_EVENT_USING_RUFFLE_SUPPORTED);
120+
return true;
121+
}
122+
else if(extCfg.com_ruffle_enabled_all)
123+
{
124+
logEvent(LOG_EVENT_USING_RUFFLE_UNSUPPORTED);
125+
return true;
126+
}
127+
128+
return false;
129+
}
130+
94131
Qx::Error CPlay::handleEntry(const Fp::Game& game)
95132
{
96133
logEvent(LOG_EVENT_ID_MATCH_TITLE.arg(game.title()));
@@ -150,12 +187,13 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game)
150187
{
151188
logEvent(LOG_EVENT_FOUND_AUTORUN.arg(addApp.name()));
152189

153-
if(sError = enqueueAdditionalApp(addApp, game.platformName(), Task::Stage::Auxiliary); sError.isValid())
190+
if(sError = enqueueAdditionalApp(addApp, game, Task::Stage::Auxiliary); sError.isValid())
154191
return sError;
155192
}
156193
}
157194

158195
// Enqueue game
196+
postDirective<DStatusUpdate>(STATUS_PLAY, game.title());
159197
if(sError = enqueueGame(game, gameData, Task::Stage::Primary); sError.isValid())
160198
return sError;
161199

@@ -168,19 +206,32 @@ Qx::Error CPlay::handleEntry(const Fp::Game& game)
168206
Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp)
169207
{
170208
logEvent(LOG_EVENT_ID_MATCH_ADDAPP.arg(addApp.name(),
171-
addApp.parentId().toString(QUuid::WithoutBraces)));
209+
addApp.parentId().toString(QUuid::WithoutBraces)));
172210

173211
Qx::Error sError;
174212
Fp::Db* db = mCore.fpInstall().database();
175213

176-
// Check if parent entry uses a data pack
214+
// Get parent info
177215
QUuid parentId = addApp.parentId();
216+
Fp::Entry parentEntry;
217+
Fp::Game parentGame;
178218
Fp::GameData parentGameData;
219+
220+
if(Fp::DbError gdErr = db->getEntry(parentEntry, parentId); gdErr.isValid())
221+
{
222+
postDirective<DError>(gdErr);
223+
return gdErr;
224+
}
225+
Q_ASSERT(std::holds_alternative<Fp::Game>(parentEntry));
226+
parentGame = std::get<Fp::Game>(parentEntry);
227+
179228
if(Fp::DbError gdErr = db->getGameData(parentGameData, parentId); gdErr.isValid())
180229
{
181230
postDirective<DError>(gdErr);
182231
return gdErr;
183232
}
233+
234+
// Check if parent entry uses a data pack
184235
bool hasDatapack = !parentGameData.isNull();
185236

186237
// Get server override (if not present, will result in the default server being used)
@@ -200,27 +251,9 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp)
200251
return sError;
201252
}
202253

203-
// Get parent info to determine platform
204-
Fp::Db::EntryFilter parentFilter{.type = Fp::Db::EntryType::Primary, .id = parentId};
205-
206-
Fp::Db::QueryBuffer parentResult;
207-
208-
if(Fp::DbError pge = db->queryEntrys(parentResult, parentFilter); pge.isValid())
209-
{
210-
postDirective<DError>(pge);
211-
return pge;
212-
}
213-
214-
// Advance result to only record
215-
parentResult.result.next();
216-
217-
// Determine platform (don't bother building entire game object since only one value is needed)
218-
QString platformName = parentResult.result.value(Fp::Db::Table_Game::COL_PLATFORM_NAME).toString();
219-
220254
// Enqueue
221255
postDirective<DStatusUpdate>(STATUS_PLAY, addApp.name());
222-
223-
if(sError = enqueueAdditionalApp(addApp, platformName, Task::Stage::Primary); sError.isValid())
256+
if(sError = enqueueAdditionalApp(addApp, parentGame, Task::Stage::Primary); sError.isValid())
224257
return sError;
225258

226259
// Enqueue service shutdown if needed
@@ -230,7 +263,7 @@ Qx::Error CPlay::handleEntry(const Fp::AddApp& addApp)
230263
return Qx::Error();
231264
}
232265

233-
Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& platform, Task::Stage taskStage)
266+
Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const Fp::Game& parent, Task::Stage taskStage)
234267
{
235268
if(addApp.appPath() == Fp::Db::Table_Add_App::ENTRY_MESSAGE)
236269
{
@@ -249,9 +282,11 @@ Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& p
249282

250283
mCore.enqueueSingleTask(extraTask);
251284
}
285+
else if(useRuffle(parent, taskStage))
286+
enqueueRuffleTask(addApp.name(), addApp.launchCommand());
252287
else
253288
{
254-
QString addAppPath = mCore.resolveFullAppPath(addApp.appPath(), platform);
289+
QString addAppPath = mCore.resolveFullAppPath(addApp.appPath(), parent.platformName());
255290
QFileInfo addAppPathInfo(addAppPath);
256291
QString param = addApp.launchCommand();
257292
addPassthroughParameters(param);
@@ -280,10 +315,18 @@ Qx::Error CPlay::enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& p
280315

281316
Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, Task::Stage taskStage)
282317
{
318+
QString param = !gameData.isNull() ? gameData.launchCommand() : game.launchCommand();
319+
320+
if(useRuffle(game, taskStage))
321+
{
322+
enqueueRuffleTask(game.title(), param);
323+
return Qx::Error();
324+
}
325+
283326
QString gamePath = mCore.resolveFullAppPath(!gameData.isNull() ? gameData.appPath() : game.appPath(),
284327
game.platformName());
285328
QFileInfo gamePathInfo(gamePath);
286-
QString param = !gameData.isNull() ? gameData.launchCommand() : game.launchCommand();
329+
287330
addPassthroughParameters(param);
288331

289332
TExec* gameTask = new TExec(mCore);
@@ -296,7 +339,6 @@ Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData,
296339
gameTask->setProcessType(TExec::ProcessType::Blocking);
297340

298341
mCore.enqueueSingleTask(gameTask);
299-
postDirective<DStatusUpdate>(STATUS_PLAY, game.title());
300342

301343
#ifdef _WIN32
302344
// Add wait task if required
@@ -308,6 +350,55 @@ Qx::Error CPlay::enqueueGame(const Fp::Game& game, const Fp::GameData& gameData,
308350
return Qx::Error();
309351
}
310352

353+
void CPlay::enqueueRuffleTask(const QString& name, const QString& originalParams)
354+
{
355+
/* Replicating:
356+
*
357+
* https://github.com/FlashpointProject/launcher/blob/54c5dd8357b9083271d36912ea9546bcd8d1cb18/extensions/core-ruffle/src/extension.ts#L65
358+
* https://github.com/FlashpointProject/launcher/blob/54c5dd8357b9083271d36912ea9546bcd8d1cb18/extensions/core-ruffle/src/middleware/standalone.ts#L181
359+
*
360+
* There is a whole system for supporting different versions of Ruffle, as well as game-specific config options, but current it's unused so we
361+
* can ignore it. Also if it isn't obvious we can only make use of standalone ruffle.
362+
*
363+
* If the ruffle config schema really starts getting used, we might want to move its setup to libfp so that it just comes with the game entry.
364+
*
365+
* The ruffle executable path should also likely be part of libfp. Then with this the chmod change should be done at startup using that path.
366+
*
367+
* TODO: Download ruffle if it's missing.
368+
*/
369+
#if defined(_WIN32)
370+
static const QString RUFFLE_EXE = u"Ruffle.exe"_s;
371+
#elif defined(__linux__)
372+
static const QString RUFFLE_EXE = u"ruffle"_s;
373+
#endif
374+
auto& fp = mCore.fpInstall();
375+
static QFileInfo ruffle = [&]{
376+
QFile file = mCore.fpInstall().dir().absoluteFilePath(u"Data/Ruffle/standalone/latest/"_s + RUFFLE_EXE);
377+
auto exec = QFile::ExeOwner | QFile::ExeGroup | QFile::ExeOther;
378+
file.setPermissions(file.permissions() | exec);
379+
return QFileInfo(file);
380+
}();
381+
382+
// This likely will need to be dynamic per game/ruffle version at some point
383+
QStringList newParams = {
384+
u"--proxy"_s,
385+
fp.preferences().browserModeProxy
386+
};
387+
addPassthroughParameters(newParams);
388+
newParams.append(QProcess::splitCommand(originalParams));
389+
390+
TExec* gameTask = new TExec(mCore);
391+
gameTask->setIdentifier(name);
392+
gameTask->setStage(Task::Stage::Primary);
393+
gameTask->setExecutable(ruffle.absoluteFilePath());
394+
gameTask->setDirectory(ruffle.absoluteDir());
395+
gameTask->setParameters(newParams);
396+
gameTask->setEnvironment(mCore.childTitleProcessEnvironment());
397+
gameTask->setProcessType(TExec::ProcessType::Blocking);
398+
399+
mCore.enqueueSingleTask(gameTask);
400+
}
401+
311402
//Protected:
312403
QList<const QCommandLineOption*> CPlay::options() const { return CL_OPTIONS_SPECIFIC + TitleCommand::options(); }
313404
QString CPlay::name() const { return NAME; }

lib/backend/src/command/c-play.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,17 @@ class CPlay : public TitleCommand
6565
static inline const QString CL_OPT_URL_L_NAME = u"url"_s;
6666
static inline const QString CL_OPT_URL_DESC = u""_s;
6767

68+
static inline const QString CL_OPT_RUFFLE_L_NAME = u"ruffle"_s;
69+
static inline const QString CL_OPT_RUFFLE_DESC = u"Forces the use of Ruffle for Flash games."_s;
70+
71+
static inline const QString CL_OPT_FLASH_L_NAME = u"flash"_s;
72+
static inline const QString CL_OPT_FLASH_DESC = u"Forces the use of the standard app (usually Flash Player) for Flash games."_s;
73+
6874
// Command line options
6975
static inline const QCommandLineOption CL_OPTION_URL{{CL_OPT_URL_S_NAME, CL_OPT_URL_L_NAME}, CL_OPT_URL_DESC, u"url"_s}; // Takes value
70-
static inline const QList<const QCommandLineOption*> CL_OPTIONS_SPECIFIC{&CL_OPTION_URL};
76+
static inline const QCommandLineOption CL_OPTION_RUFFLE{{CL_OPT_RUFFLE_L_NAME}, CL_OPT_URL_DESC}; // Boolean
77+
static inline const QCommandLineOption CL_OPTION_FLASH{{CL_OPT_FLASH_L_NAME}, CL_OPT_FLASH_DESC}; // Boolean
78+
static inline const QList<const QCommandLineOption*> CL_OPTIONS_SPECIFIC{&CL_OPTION_URL, &CL_OPTION_RUFFLE, &CL_OPTION_FLASH};
7179

7280
// Logging - Messages
7381
static inline const QString LOG_EVENT_HANDLING_AUTO = u"Handling automatic tasks..."_s;
@@ -77,6 +85,10 @@ class CPlay : public TitleCommand
7785
static inline const QString LOG_EVENT_FOUND_AUTORUN = u"Found autorun-before additional app: %1"_s;
7886
static inline const QString LOG_EVENT_DATA_PACK_TITLE = u"Selected title uses a data pack"_s;
7987
static inline const QString LOG_EVENT_SERVER_OVERRIDE = u"Selected title overrides the server to: %1"_s;
88+
static inline const QString LOG_EVENT_USING_RUFFLE_SUPPORTED = u"Using Ruffle for this title (supported)"_s;
89+
static inline const QString LOG_EVENT_USING_RUFFLE_UNSUPPORTED = u"Using Ruffle for this title (unsupported)"_s;
90+
static inline const QString LOG_EVENT_FORCING_RUFFLE = u"Forcing the use of Ruffle for this title"_s;
91+
static inline const QString LOG_EVENT_FORCING_FLASH = u"Forcing the use of the standard Flash application for this title"_s;
8092

8193
public:
8294
// Meta
@@ -95,11 +107,14 @@ class CPlay : public TitleCommand
95107
//-Instance Functions------------------------------------------------------------------------------------------------------
96108
private:
97109
void addPassthroughParameters(QString& param);
110+
void addPassthroughParameters(QStringList& param);
98111
QString getServerOverride(const Fp::GameData& gd);
112+
bool useRuffle(const Fp::Game& game, Task::Stage stage);
99113
Qx::Error handleEntry(const Fp::Game& game);
100114
Qx::Error handleEntry(const Fp::AddApp& addApp);
101-
Qx::Error enqueueAdditionalApp(const Fp::AddApp& addApp, const QString& platform, Task::Stage taskStage);
115+
Qx::Error enqueueAdditionalApp(const Fp::AddApp& addApp, const Fp::Game& parent, Task::Stage taskStage);
102116
Qx::Error enqueueGame(const Fp::Game& game, const Fp::GameData& gameData, Task::Stage taskStage);
117+
void enqueueRuffleTask(const QString& name, const QString& originalParams);
103118

104119
protected:
105120
QList<const QCommandLineOption*> options() const override;

lib/backend/src/kernel/core.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,9 @@ void Core::attachFlashpoint(std::unique_ptr<Fp::Install> flashpointInstall)
371371
//-Mimic startup script setup-------------------------
372372
logEvent(LOG_EVENT_LINUX_SPECIFIC_STARTUP_STEPS);
373373

374-
// NOTE: This check likely will need to be modified over time, though it's how the script does it
375-
bool immutable = mFlashpointInstall->dir().exists(u"Libraries"_s);
374+
// NOTE: This check likely will need to be modified over time, though it's close to how the script does it
375+
QDir librariesDir(mFlashpointInstall->dir().absoluteFilePath(u"Libraries"_s));
376+
bool immutable = !librariesDir.entryList({"*.so"}, QDir::Files).isEmpty();
376377
logEvent(LOG_EVENT_LINUX_BUILD_TYPE.arg(immutable ? u"isn't"_s : u"is"_s));
377378

378379
if(immutable)
@@ -392,8 +393,8 @@ void Core::attachFlashpoint(std::unique_ptr<Fp::Install> flashpointInstall)
392393
de.insert(u"PATH"_s, pathChange);
393394
qputenv("PATH", pathChange.toLocal8Bit());
394395
}
395-
else
396-
de.insert(u"WINEPREFIX"_s, fpPath + u"/FPSoftware/Wine"_s);
396+
397+
de.insert(u"WINEPREFIX"_s, fpPath + u"/FPSoftware/Wine"_s);
397398
#endif
398399

399400
TExec::setDefaultProcessEnvironment(de);

lib/backend/src/kernel/core.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
#include "task/task.h"
2222
#include "_backend_project_vars.h"
2323

24-
// TODO: It's time to cleanout the old unused mounter support (i.e. docker, QEMU, etc).
24+
/* TODO: It's time to cleanout the old unused mounter support (i.e. docker, QEMU, etc),
25+
* though maybe keep them a bit longer for if we ever get to adding support for other
26+
* BM projects, as those might still use that tech
27+
*/
2528

2629
class QX_ERROR_TYPE(CoreError, "CoreError", 1200)
2730
{

0 commit comments

Comments
 (0)