Skip to content

Commit

Permalink
Added --random switch
Browse files Browse the repository at this point in the history
  • Loading branch information
oblivioncth committed Sep 19, 2020
1 parent 57e358c commit 82c93e3
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 15 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Each release of this application targets a specific version or versions of BlueM
|--|--|
| 0.1 | 8.1 ("Spirit of Adventure") |
| 0.1.1 | 8.2 ("Approaching Planet Nine") |
| 0.2, 0.3 | 8.1, 8.2+ |
| 0.2+ | 8.1+ |

Using a version of CLIFp that does not target the version of Flashpoint you wish to use it with is highly discouraged as some features may not work correctly or at all and in some cases the utility may fail to function entirely; **however since 0.2 compatibility with newer versions is quite likely even if they aren't explicit listed yet** (usually because I haven't had time to check if an update is needed).

Expand All @@ -34,7 +34,7 @@ CLIFp was primarily created for use with its sister project [OFILb (Obby's Flas
That being said, it is perfectly possible to use CLIFp with Flashpoint alone in any manner you see fit.

### General
While CLIFp has multiple functions, typical usage involves one of two main methods:
While CLIFp has multiple functions, typical usage involves one of three main methods:

**Auto:**
This is the most straightforward and hassle free approach. Simply use the **--auto** or **-a** switch followed by the GUID/UUID of the game/animation you wish to start, as seen in the following example:
Expand All @@ -44,6 +44,9 @@ You can find a title's ID by right clicking on an entry in Flashpoint and select

If the ID belongs to a main game/animation entry, any additional apps that are marked as auto-run before will also be started ahead of time just as they would be when using the Flashpoint Launcher.

**Random:**
This mode works exactly the same as Auto mode except that the title ID is selected at random (uniformly) for you from the pool of all playable games/animations. A title qualifies as playable if it is a main title who's status is not "Not Working" or if it is an additional-app that is not a message, extra, or otherwise Autostart-before entry.

**App/Param:**
This method can only start one application at a time. Use the **-x/--exe** switch to specify the relative (from Flashpoint's directory) path to the application to launch and the **-p/--param** switch to provide the launch arguments that will be passed to the target application.

Expand All @@ -56,12 +59,13 @@ The applications and arguments that are used for each game/animation can be foun
- **-x | --exe:** Relative (to Flashpoint Directory) of primary application to launch
- **-p | --param:** Command-line parameters to use when starting the primary application
- **-a | --auto:** Finds a game/additional-app by UUID and runs it if found, including run-before additional apps in the case of a game
- **-r | --random:** Selects a random game UUID from the database and starts it in the same manner as using the --auto switch.
- **-m | --msg:** Displays an pop-up dialog with the supplied message. Used primarily for some additional apps
- **-e | --extra:** Opens an explorer window to the specified extra. Used primarily for some additional apps
- **-q | --quiet:** Silences all non-critical error messages
- **-s | --silent:** Silences all error messages (takes precedence over quiet mode)

Use **'exe'** and **'param'** for normal operation, use **'auto'** by itself for automatic operation, use **'msg'** to display a popup message, use **'extra'** to view an extra, or use **'help'** and/or **'version'** for information.
Use **'exe'** and **'param'** for normal operation, use **'auto'** by itself for automatic operation, use **'random'** by itself for random operation, use **'msg'** to display a popup message, use **'extra'** to view an extra, or use **'help'** and/or **'version'** for information.

**NOTE:** When using the **--exe** and **--param** switches all quotes that are part of the input itself must be escaped for the command to be passed correctly. For example, the launch command

Expand Down
40 changes: 39 additions & 1 deletion src/flashpointinstall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ QSqlError Install::initialPlaylistGameQuery(QList<DBQueryBuffer>& resultBuffer,
return QSqlError();
}

QSqlError Install::queryEntryID(DBQueryBuffer& resultBuffer, QUuid appID) const
QSqlError Install::queryEntryByID(DBQueryBuffer& resultBuffer, QUuid appID) const
{
// Ensure return buffer is effectively null
resultBuffer = DBQueryBuffer();
Expand Down Expand Up @@ -842,6 +842,44 @@ QSqlError Install::queryEntryAddApps(DBQueryBuffer& resultBuffer, QUuid appID) c
return makeNonBindQuery(resultBuffer, &fpDB, mainQueryCommand, sizeQueryCommand);
}

QSqlError Install::queryAllGameIDs(DBQueryBuffer& resultBuffer)
{
// Ensure return buffer is effectively null
resultBuffer = DBQueryBuffer();

// Get database
QSqlDatabase fpDB = getThreadedDatabaseConnection();

// Make query
QString baseQueryCommand = "SELECT %1 FROM " + DBTable_Game::NAME + " WHERE " +
DBTable_Game::COL_STATUS + " != '" + DBTable_Game::ENTRY_NOT_WORK + "'";
QString mainQueryCommand = baseQueryCommand.arg("`" + DBTable_Game::COL_ID + "`");
QString sizeQueryCommand = baseQueryCommand.arg(GENERAL_QUERY_SIZE_COMMAND);

resultBuffer.source = DBTable_Game::NAME;
return makeNonBindQuery(resultBuffer, &fpDB, mainQueryCommand, sizeQueryCommand);
}

QSqlError Install::queryAllMainAddAppIDs(DBQueryBuffer& resultBuffer)
{
// Ensure return buffer is effectively null
resultBuffer = DBQueryBuffer();

// Get database
QSqlDatabase fpDB = getThreadedDatabaseConnection();

// Make query
QString baseQueryCommand = "SELECT %1 FROM " + DBTable_Add_App::NAME + " WHERE " +
DBTable_Add_App::COL_APP_PATH + " NOT IN ('" + DBTable_Add_App::ENTRY_EXTRAS +
"','" + DBTable_Add_App::ENTRY_MESSAGE + "') AND " + DBTable_Add_App::COL_AUTORUN +
" != 1";
QString mainQueryCommand = baseQueryCommand.arg("`" + DBTable_Add_App::COL_ID + "`");
QString sizeQueryCommand = baseQueryCommand.arg(GENERAL_QUERY_SIZE_COMMAND);

resultBuffer.source = DBTable_Game::NAME;
return makeNonBindQuery(resultBuffer, &fpDB, mainQueryCommand, sizeQueryCommand);
}

QString Install::getPath() const { return mRootDirectory.absolutePath(); }
QStringList Install::getPlatformList() const { return mPlatformList; }
QStringList Install::getPlaylistList() const { return mPlaylistList; }
Expand Down
5 changes: 4 additions & 1 deletion src/flashpointinstall.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class Install
COL_VERSION, COL_ORIGINAL_DESC, COL_LANGUAGE, COL_LIBRARY, COL_ORDER_TITLE};

static inline const QString GAME_LIBRARY = "arcade";
static inline const QString ENTRY_NOT_WORK = "Not Working";
};

class DBTable_Add_App
Expand Down Expand Up @@ -338,8 +339,10 @@ class Install
QSqlError initialPlaylistGameQuery(QList<DBQueryBuffer>& resultBuffer, const QList<QUuid>& knownPlaylistsToQuery) const;

// Queries - CLIFp
QSqlError queryEntryID(DBQueryBuffer& resultBuffer, QUuid appID) const;
QSqlError queryEntryByID(DBQueryBuffer& resultBuffer, QUuid appID) const;
QSqlError queryEntryAddApps(DBQueryBuffer& resultBuffer, QUuid appID) const;
QSqlError queryAllGameIDs(DBQueryBuffer& resultBuffer);
QSqlError queryAllMainAddAppIDs(DBQueryBuffer& resultBuffer);

// Data access
QString getPath() const;
Expand Down
109 changes: 102 additions & 7 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ enum ErrorCode
CANT_READ_BAT_FILE = 0x14
};

enum class OperationMode { Invalid, Normal, Auto, Message, Extra, Information };
enum class OperationMode { Invalid, Normal, Auto, Random, Message, Extra, Information };
enum class TaskType { Startup, Primary, Auxiliary, Wait, Shutdown };
enum class ProcessType { Blocking, Deferred, Detached };
enum class ErrorVerbosity { Full, Quiet, Silent };
Expand Down Expand Up @@ -87,6 +87,10 @@ const QString CL_OPT_AUTO_S_NAME = "a";
const QString CL_OPT_AUTO_L_NAME = "auto";
const QString CL_OPT_AUTO_DESC = "Finds a game/additional-app by UUID and runs it if found, including run-before additional apps in the case of a game.";

const QString CL_OPT_RAND_S_NAME = "r";
const QString CL_OPT_RAND_L_NAME = "random";
const QString CL_OPT_RAND_DESC = "Selects a random game UUID from the database and starts it in the same manner as using the --" + CL_OPT_AUTO_L_NAME + " switch.";

const QString CL_OPT_QUIET_S_NAME = "q";
const QString CL_OPT_QUIET_L_NAME = "quiet";
const QString CL_OPT_QUIET_DESC = "Silences all non-critical error messages.";
Expand All @@ -104,14 +108,15 @@ const QString CL_HELP_MESSAGE =
"<b>-" + CL_OPT_APP_S_NAME + " | --" + CL_OPT_APP_L_NAME + ":</b> &nbsp;" + CL_OPT_APP_DESC + "<br>"
"<b>-" + CL_OPT_PARAM_S_NAME + " | --" + CL_OPT_PARAM_L_NAME + ":</b> &nbsp;" + CL_OPT_PARAM_DESC + "<br>"
"<b>-" + CL_OPT_AUTO_S_NAME + " | --" + CL_OPT_AUTO_L_NAME + ":</b> &nbsp;" + CL_OPT_AUTO_DESC + "<br>"
"<b>-" + CL_OPT_RAND_S_NAME + " | --" + CL_OPT_RAND_L_NAME + ":</b> &nbsp;" + CL_OPT_RAND_DESC + "<br>"
"<b>-" + CL_OPT_MSG_S_NAME + " | --" + CL_OPT_MSG_L_NAME + ":</b> &nbsp;" + CL_OPT_MSG_DESC + "<br>"
"<b>-" + CL_OPT_EXTRA_S_NAME + " | --" + CL_OPT_EXTRA_L_NAME + ":</b> &nbsp;" + CL_OPT_EXTRA_DESC + "<br>"
"<b>-" + CL_OPT_QUIET_S_NAME + " | --" + CL_OPT_QUIET_L_NAME + ":</b> &nbsp;" + CL_OPT_QUIET_DESC + "<br>"
"<b>-" + CL_OPT_SILENT_S_NAME + " | --" + CL_OPT_SILENT_L_NAME + ":</b> &nbsp;" + CL_OPT_SILENT_DESC + "<br>"
"<br>"
"Use <b>'" + CL_OPT_APP_L_NAME + "'</b> and <b>'" + CL_OPT_PARAM_L_NAME + "'</b> for normal operation, use <b>'" + CL_OPT_AUTO_L_NAME +
"'</b> by itself for automatic operation, use <b>'" + CL_OPT_MSG_L_NAME + "'</b> to display a popup message, use <b>'" + CL_OPT_EXTRA_L_NAME +
"'</b> to view an extra, or use <b>'" + CL_OPT_HELP_L_NAME + "'</b> and/or <b>'" + CL_OPT_VERSION_L_NAME + "'</b> for information.";
"'</b> by itself for automatic operation, use <b>'" + CL_OPT_MSG_L_NAME + "'</b> by itself for random operation, use <b>'" + CL_OPT_MSG_L_NAME +
"'</b> to display a popup message, use <b>'" + CL_OPT_EXTRA_L_NAME + "'</b> to view an extra, or use <b>'" + CL_OPT_HELP_L_NAME +
"'</b> and/or <b>'" + CL_OPT_VERSION_L_NAME + "'</b> for information.";

const QString CL_VERSION_MESSAGE = "CLI Flashpoint version " VER_PRODUCTVERSION_STR ", designed for use with BlueMaxima's Flashpoint " VER_PRODUCTVERSION_STR "+";

Expand Down Expand Up @@ -149,20 +154,22 @@ const QString WRN_WAIT_PROCESS_NOT_HOOKED_S = "The title may not work correctly"
const QCommandLineOption CL_OPTION_APP({CL_OPT_APP_S_NAME, CL_OPT_APP_L_NAME}, CL_OPT_APP_DESC, "application"); // Takes value
const QCommandLineOption CL_OPTION_PARAM({CL_OPT_PARAM_S_NAME, CL_OPT_PARAM_L_NAME}, CL_OPT_PARAM_DESC, "parameters"); // Takes value
const QCommandLineOption CL_OPTION_AUTO({CL_OPT_AUTO_S_NAME, CL_OPT_AUTO_L_NAME}, CL_OPT_AUTO_DESC, "id"); // Takes value
const QCommandLineOption CL_OPTION_RAND({CL_OPT_RAND_S_NAME, CL_OPT_RAND_L_NAME}, CL_OPT_RAND_DESC); // Boolean option
const QCommandLineOption CL_OPTION_MSG({CL_OPT_MSG_S_NAME, CL_OPT_MSG_L_NAME}, CL_OPT_MSG_DESC, "message"); // Takes value
const QCommandLineOption CL_OPTION_EXTRA({CL_OPT_EXTRA_S_NAME, CL_OPT_EXTRA_L_NAME}, CL_OPT_EXTRA_DESC, "extra"); // Takes value
const QCommandLineOption CL_OPTION_HELP({CL_OPT_HELP_S_NAME, CL_OPT_HELP_L_NAME, CL_OPT_HELP_E_NAME}, CL_OPT_HELP_DESC); // Boolean option
const QCommandLineOption CL_OPTION_VERSION({CL_OPT_VERSION_S_NAME, CL_OPT_VERSION_L_NAME}, CL_OPT_VERSION_DESC); // Boolean option
const QCommandLineOption CL_OPTION_QUIET({CL_OPT_QUIET_S_NAME, CL_OPT_QUIET_L_NAME}, CL_OPT_QUIET_DESC); // Boolean option
const QCommandLineOption CL_OPTION_SILENT({CL_OPT_SILENT_S_NAME, CL_OPT_SILENT_L_NAME}, CL_OPT_SILENT_DESC); // Boolean option
const QList<const QCommandLineOption*> CL_OPTIONS_MAIN{&CL_OPTION_APP, &CL_OPTION_PARAM, &CL_OPTION_AUTO,
&CL_OPTION_MSG, &CL_OPTION_EXTRA, &CL_OPTION_HELP, &CL_OPTION_VERSION};
const QList<const QCommandLineOption*> CL_OPTIONS_MAIN{&CL_OPTION_APP, &CL_OPTION_PARAM, &CL_OPTION_AUTO, &CL_OPTION_MSG,
&CL_OPTION_EXTRA, &CL_OPTION_HELP, &CL_OPTION_VERSION, &CL_OPTION_RAND};
const QList<const QCommandLineOption*> CL_OPTIONS_ALL = CL_OPTIONS_MAIN + QList<const QCommandLineOption*>{&CL_OPTION_QUIET, &CL_OPTION_SILENT};

// CLI Option Operation Mode Map TODO: Submit a patch for Qt6 to make QCommandLineOption directly hashable (implement == and qHash)
const QHash<QSet<QString>, OperationMode> CL_MAIN_OPTIONS_OP_MODE_MAP{
{{CL_OPT_APP_S_NAME, CL_OPT_PARAM_S_NAME}, OperationMode::Normal},
{{CL_OPT_AUTO_S_NAME}, OperationMode::Auto},
{{CL_OPT_RAND_S_NAME}, OperationMode::Random},
{{CL_OPT_MSG_S_NAME}, OperationMode::Message},
{{CL_OPT_EXTRA_S_NAME}, OperationMode::Extra},
{{CL_OPT_HELP_S_NAME}, OperationMode::Information},
Expand Down Expand Up @@ -190,6 +197,7 @@ const int LOG_MAX_ENTRIES = 50;
// Logging - Messages
const QString LOG_ERR_INVALID_PARAM = "Invalid combination of parameters used";
const QString LOG_ERR_CRITICAL = "Aborting execution due to previous critical errors";
const QString LOG_WRN_INVALID_RAND_ID = "A UUID found in the database during Random operation is invalid (%1)";
const QString LOG_EVENT_FLASHPOINT_LINK = "Linked to Flashpoint install at: %1";
const QString LOG_EVENT_OP_MODE = "Operation Mode: %1";
const QString LOG_EVENT_APP_TASK = "Enqueued App Task: {.type = %1, .path = \"%2\", .filename = \"%3\", "
Expand All @@ -200,6 +208,9 @@ const QString LOG_EVENT_HELP_SHOWN = "Displayed help information";
const QString LOG_EVENT_VER_SHOWN = "Displayed version information";
const QString LOG_EVENT_INIT = "Initializing CLIFp...";
const QString LOG_EVENT_GET_SET = "Reading Flashpoint configuration...";
const QString LOG_EVENT_SEL_RAND = "Selecting a playable game at random...";
const QString LOG_EVENT_RAND_ID = "Randomly chose game \"%1\"";
const QString LOG_EVENT_PLAYABLE_COUNT = "Found %1 playable games";
const QString LOG_EVENT_ENQ_START = "Enqueuing startup tasks...";
const QString LOG_EVENT_ENQ_AUTO = "Enqueuing automatic tasks...";
const QString LOG_EVENT_ENQ_STOP = "Enqueuing shutdown tasks...";
Expand Down Expand Up @@ -244,6 +255,7 @@ void cleanup(FP::Install& fpInstall, QList<QProcess*>& childProcesses);

// Prototypes - Helper
QString getRawCommandLineParams();
ErrorCode randomlySelectID(QUuid& idBuffer, FP::Install& fpInstall);
Qx::GenericError appInvolvesSecurePlayer(bool& involvesBuffer, QFileInfo appInfo);
QString escapeNativeArgsForCMD(QString nativeArgs);
void postError(Qx::GenericError error, bool log = true);
Expand Down Expand Up @@ -417,6 +429,20 @@ int main(int argc, char *argv[])
if((enqueueError = enqueueConditionalWaitTask(appTaskQueue, inputInfo)))
return printLogAndExit(enqueueError);
break;
case OperationMode::Random:
if((enqueueError = openAndVerifyProperDatabase(flashpointInstall)))
return printLogAndExit(enqueueError);

if((enqueueError = randomlySelectID(autoID, flashpointInstall)))
return printLogAndExit(enqueueError);

if((enqueueError = enqueueStartupTasks(appTaskQueue, flashpointConfig, flashpointServices)))
return printLogAndExit(enqueueError);

if((enqueueError = enqueueAutomaticTasks(appTaskQueue, autoID, flashpointInstall)))
return printLogAndExit(enqueueError);
break;


case OperationMode::Auto:
if((autoID = QUuid(clParser.value(CL_OPTION_AUTO))).isNull())
Expand Down Expand Up @@ -567,7 +593,7 @@ ErrorCode enqueueAutomaticTasks(std::queue<AppTask>& taskQueue, QUuid targetID,
FP::Install::DBQueryBuffer searchResult;
ErrorCode enqueueError;

searchError = fpInstall.queryEntryID(searchResult, targetID);
searchError = fpInstall.queryEntryByID(searchResult, targetID);
if(searchError.isValid())
{
postError(Qx::GenericError(Qx::GenericError::Critical, ERR_UNEXPECTED_SQL, searchError.text()));
Expand Down Expand Up @@ -994,6 +1020,75 @@ QString getRawCommandLineParams()
return QString::fromStdWString(std::wstring(rawCL));
}

ErrorCode randomlySelectID(QUuid& idBuffer, FP::Install& fpInstall)
{
logEvent(LOG_EVENT_SEL_RAND);

// Reset buffer
idBuffer = QUuid();

// SQL Error tracker
QSqlError searchError;

// Query all main games
FP::Install::DBQueryBuffer mainGameIDQuery;
searchError = fpInstall.queryAllGameIDs(mainGameIDQuery);
if(searchError.isValid())
{
postError(Qx::GenericError(Qx::GenericError::Critical, ERR_UNEXPECTED_SQL, searchError.text()));
return SQL_ERROR;
}

// Query all main additional apps
FP::Install::DBQueryBuffer mainAddAppIDQuery;
searchError = fpInstall.queryAllGameIDs(mainAddAppIDQuery);
if(searchError.isValid())
{
postError(Qx::GenericError(Qx::GenericError::Critical, ERR_UNEXPECTED_SQL, searchError.text()));
return SQL_ERROR;
}

QList<QUuid> playableIDs;

// Enumerate main game IDs
for(int i = 0; i < mainGameIDQuery.size; i++)
{
// Go to next record
mainGameIDQuery.result.next();

// Add ID to list
QString gameIDString = mainGameIDQuery.result.value(FP::Install::DBTable_Game::COL_ID).toString();
QUuid gameID = QUuid(gameIDString);
if(!gameID.isNull())
playableIDs.append(gameID);
else
logError(Qx::GenericError(Qx::GenericError::Warning, LOG_WRN_INVALID_RAND_ID.arg(gameIDString)));
}

// Enumerate main additional app IDs
for(int i = 0; i < mainAddAppIDQuery.size; i++)
{
// Go to next record
mainAddAppIDQuery.result.next();

// Create ID and add if valid (should always be)
QString gameIDString = mainAddAppIDQuery.result.value(FP::Install::DBTable_Game::COL_ID).toString();
QUuid gameID = QUuid(gameIDString);
if(!gameID.isNull())
playableIDs.append(gameID);
else
logError(Qx::GenericError(Qx::GenericError::Warning, LOG_WRN_INVALID_RAND_ID.arg(gameIDString)));
}
logEvent(LOG_EVENT_PLAYABLE_COUNT.arg(QLocale(QLocale::system()).toString(playableIDs.size())));

// Set buffer to random ID
idBuffer = playableIDs.value(QRandomGenerator::global()->bounded(playableIDs.size() - 1));
logEvent(LOG_EVENT_RAND_ID.arg(idBuffer.toString(QUuid::WithoutBraces)));

// Return success
return NO_ERR;
}

Qx::GenericError appInvolvesSecurePlayer(bool& involvesBuffer, QFileInfo appInfo)
{
// Reset buffer
Expand Down
4 changes: 2 additions & 2 deletions src/version.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef VERSION_H
#define VERSION_H

#define VER_FILEVERSION 0,3,0,0
#define VER_FILEVERSION_STR "0.3.0.0"
#define VER_FILEVERSION 0,3,1,0
#define VER_FILEVERSION_STR "0.3.1.0"

#define VER_PRODUCTVERSION 8,2
#define VER_PRODUCTVERSION_STR "8.2"
Expand Down

0 comments on commit 82c93e3

Please sign in to comment.