2022-03-19 11:46:56 +00:00
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright ( C ) 2022 Sefa Eyeoglu < contact @ scrumplex . net >
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , version 3.
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice :
*
* Copyright 2013 - 2021 MultiMC Contributors
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
2016-08-10 22:44:01 +00:00
# include "LaunchController.h"
2021-11-22 02:55:16 +00:00
# include "minecraft/auth/AccountList.h"
2021-11-20 15:22:22 +00:00
# include "Application.h"
2021-11-22 02:55:16 +00:00
# include "ui/MainWindow.h"
# include "ui/InstanceWindow.h"
# include "ui/dialogs/CustomMessageBox.h"
# include "ui/dialogs/ProfileSelectDialog.h"
# include "ui/dialogs/ProgressDialog.h"
# include "ui/dialogs/EditAccountDialog.h"
# include "ui/dialogs/ProfileSetupDialog.h"
2015-07-04 23:54:30 +00:00
# include <QLineEdit>
# include <QInputDialog>
2015-08-14 00:27:01 +00:00
# include <QStringList>
2021-06-19 01:15:21 +00:00
# include <QHostInfo>
# include <QList>
# include <QHostAddress>
2021-12-30 20:26:29 +00:00
# include <QPushButton>
2021-11-22 02:55:16 +00:00
# include "BuildConfig.h"
# include "JavaCommon.h"
# include "tasks/Task.h"
# include "minecraft/auth/AccountTask.h"
# include "launch/steps/TextPrint.h"
2015-07-04 23:54:30 +00:00
2015-10-23 22:57:54 +00:00
LaunchController : : LaunchController ( QObject * parent ) : Task ( parent )
2015-07-04 23:54:30 +00:00
{
}
2015-10-23 22:57:54 +00:00
void LaunchController : : executeTask ( )
2015-07-04 23:54:30 +00:00
{
2018-07-15 12:51:05 +00:00
if ( ! m_instance )
{
2019-09-15 03:31:13 +00:00
emitFailed ( tr ( " No instance specified! " ) ) ;
2018-07-15 12:51:05 +00:00
return ;
}
2015-07-04 23:54:30 +00:00
2022-04-06 20:45:57 +00:00
if ( ! JavaCommon : : checkJVMArgs ( m_instance - > settings ( ) - > get ( " JvmArgs " ) . toString ( ) , m_parentWidget ) ) {
emitFailed ( tr ( " Invalid Java arguments specified. Please fix this first. " ) ) ;
return ;
}
2021-12-04 00:18:05 +00:00
2018-07-15 12:51:05 +00:00
login ( ) ;
2017-12-03 13:05:35 +00:00
}
2021-10-31 20:42:06 +00:00
void LaunchController : : decideAccount ( )
{
if ( m_accountToUse ) {
return ;
}
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
// Find an account to use.
2021-11-20 15:22:22 +00:00
auto accounts = APPLICATION - > accounts ( ) ;
2018-07-15 12:51:05 +00:00
if ( accounts - > count ( ) < = 0 )
{
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox : : selectable (
2021-07-26 19:44:11 +00:00
m_parentWidget ,
tr ( " No Accounts " ) ,
2022-10-09 11:19:38 +00:00
tr ( " In order to play Minecraft, you must have at least one Microsoft or Mojang "
2022-10-09 11:20:50 +00:00
" account logged in. Mojang accounts can only be used offline. "
2018-07-15 12:51:05 +00:00
" Would you like to open the account manager to add an account now? " ) ,
2021-07-26 19:44:11 +00:00
QMessageBox : : Information ,
QMessageBox : : Yes | QMessageBox : : No
) - > exec ( ) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
if ( reply = = QMessageBox : : Yes )
{
// Open the account manager.
2021-11-20 15:22:22 +00:00
APPLICATION - > ShowGlobalSettings ( m_parentWidget , " accounts " ) ;
2018-07-15 12:51:05 +00:00
}
2022-05-30 10:33:07 +00:00
else if ( reply = = QMessageBox : : No )
{
// Do not open "profile select" dialog.
return ;
}
2018-07-15 12:51:05 +00:00
}
2021-07-26 19:44:11 +00:00
2022-11-01 14:36:44 +00:00
bool overrideAccount = m_instance - > settings ( ) - > get ( " OverrideAccount " ) . toBool ( ) ;
2022-11-17 02:08:19 +00:00
QString overrideAccountProfileId = m_instance - > settings ( ) - > get ( " OverrideAccountProfileId " ) . toString ( ) ;
m_accountToUse = accounts - > defaultAccount ( ) ;
if ( overrideAccount ) {
int overrideIndex = accounts - > findAccountByProfileId ( overrideAccountProfileId ) ;
if ( overrideIndex ! = - 1 ) {
m_accountToUse = accounts - > at ( overrideIndex ) ;
}
}
2022-11-01 14:36:44 +00:00
2021-11-20 15:22:22 +00:00
if ( ! m_accountToUse )
2018-07-15 12:51:05 +00:00
{
// If no default account is set, ask the user which one to use.
2021-07-26 19:44:11 +00:00
ProfileSelectDialog selectDialog (
tr ( " Which account would you like to use? " ) ,
ProfileSelectDialog : : GlobalDefaultCheckbox ,
m_parentWidget
) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
selectDialog . exec ( ) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
// Launch the instance with the selected account.
2021-10-31 20:42:06 +00:00
m_accountToUse = selectDialog . selectedAccount ( ) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
// If the user said to use the account as default, do that.
2021-10-31 20:42:06 +00:00
if ( selectDialog . useAsGlobalDefault ( ) & & m_accountToUse ) {
2021-11-20 15:22:22 +00:00
accounts - > setDefaultAccount ( m_accountToUse ) ;
2021-07-26 19:44:11 +00:00
}
2018-07-15 12:51:05 +00:00
}
2021-10-31 20:42:06 +00:00
}
void LaunchController : : login ( ) {
decideAccount ( ) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
// if no account is selected, we bail
2021-10-31 20:42:06 +00:00
if ( ! m_accountToUse )
2018-07-15 12:51:05 +00:00
{
2019-09-15 03:31:13 +00:00
emitFailed ( tr ( " No account selected for launch. " ) ) ;
2018-07-15 12:51:05 +00:00
return ;
}
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
// we loop until the user succeeds in logging in or gives up
bool tryagain = true ;
2022-08-03 18:37:10 +00:00
unsigned int tries = 0 ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
while ( tryagain )
{
2022-08-03 18:37:10 +00:00
if ( tries > 0 & & tries % 3 = = 0 ) {
auto result = QMessageBox : : question (
m_parentWidget ,
tr ( " Continue launch? " ) ,
tr ( " It looks like we couldn't launch after %1 tries. Do you want to continue trying? " )
. arg ( tries )
) ;
if ( result = = QMessageBox : : No ) {
2022-08-04 08:02:54 +00:00
emitAborted ( ) ;
2022-08-03 18:37:10 +00:00
return ;
}
}
tries + + ;
2018-07-15 12:51:05 +00:00
m_session = std : : make_shared < AuthSession > ( ) ;
m_session - > wants_online = m_online ;
2022-07-11 18:46:11 +00:00
m_session - > demo = m_demo ;
2021-12-04 00:18:05 +00:00
m_accountToUse - > fillSession ( m_session ) ;
2021-07-26 19:44:11 +00:00
2022-01-17 11:08:10 +00:00
// Launch immediately in true offline mode
if ( m_accountToUse - > isOffline ( ) ) {
launchInstance ( ) ;
return ;
}
2021-12-04 00:18:05 +00:00
switch ( m_accountToUse - > accountState ( ) ) {
case AccountState : : Offline : {
2021-12-06 19:17:31 +00:00
m_session - > wants_online = false ;
2023-05-07 07:55:56 +00:00
[[fallthrough]] ;
2018-07-15 12:51:05 +00:00
}
2021-12-04 00:18:05 +00:00
case AccountState : : Online : {
2021-12-06 19:17:31 +00:00
if ( ! m_session - > wants_online ) {
// we ask the user for a player name
bool ok = false ;
2022-07-11 18:46:11 +00:00
QString message = tr ( " Choose your offline mode player name. " ) ;
if ( m_session - > demo ) {
message = tr ( " Choose your demo mode player name. " ) ;
}
2022-03-28 12:40:34 +00:00
QString lastOfflinePlayerName = APPLICATION - > settings ( ) - > get ( " LastOfflinePlayerName " ) . toString ( ) ;
QString usedname = lastOfflinePlayerName . isEmpty ( ) ? m_session - > player_name : lastOfflinePlayerName ;
2021-12-06 19:17:31 +00:00
QString name = QInputDialog : : getText (
m_parentWidget ,
tr ( " Player name " ) ,
2022-07-11 18:46:11 +00:00
message ,
2021-12-06 19:17:31 +00:00
QLineEdit : : Normal ,
2022-03-28 12:40:34 +00:00
usedname ,
2021-12-06 19:17:31 +00:00
& ok
) ;
if ( ! ok )
{
tryagain = false ;
break ;
}
if ( name . length ( ) )
{
usedname = name ;
2022-03-28 12:40:34 +00:00
APPLICATION - > settings ( ) - > set ( " LastOfflinePlayerName " , usedname ) ;
2021-12-06 19:17:31 +00:00
}
m_session - > MakeOffline ( usedname ) ;
}
2021-12-30 20:26:29 +00:00
if ( m_accountToUse - > ownsMinecraft ( ) ) {
if ( ! m_accountToUse - > hasProfile ( ) ) {
// Now handle setting up a profile name here...
ProfileSetupDialog dialog ( m_accountToUse , m_parentWidget ) ;
if ( dialog . exec ( ) = = QDialog : : Accepted )
{
tryagain = true ;
continue ;
}
else
{
emitFailed ( tr ( " Received undetermined session status during login. " ) ) ;
return ;
}
2021-12-04 00:18:05 +00:00
}
2021-12-30 20:26:29 +00:00
// we own Minecraft, there is a profile, it's all ready to go!
launchInstance ( ) ;
return ;
2021-11-10 02:02:51 +00:00
}
2021-12-04 00:18:05 +00:00
else {
2021-12-30 20:26:29 +00:00
// play demo ?
QMessageBox box ( m_parentWidget ) ;
box . setWindowTitle ( tr ( " Play demo? " ) ) ;
box . setText ( tr ( " This account does not own Minecraft. \n You need to purchase the game first to play it. \n \n Do you want to play the demo? " ) ) ;
box . setIcon ( QMessageBox : : Warning ) ;
auto demoButton = box . addButton ( tr ( " Play Demo " ) , QMessageBox : : ButtonRole : : YesRole ) ;
auto cancelButton = box . addButton ( tr ( " Cancel " ) , QMessageBox : : ButtonRole : : NoRole ) ;
box . setDefaultButton ( cancelButton ) ;
box . exec ( ) ;
if ( box . clickedButton ( ) = = demoButton ) {
// play demo here
m_session - > MakeDemo ( ) ;
launchInstance ( ) ;
}
else {
emitFailed ( tr ( " Launch cancelled - account does not own Minecraft. " ) ) ;
}
2021-11-10 02:02:51 +00:00
}
2021-12-04 00:18:05 +00:00
return ;
}
2021-12-05 02:48:07 +00:00
case AccountState : : Errored :
// This means some sort of soft error that we can fix with a refresh ... so let's refresh.
2021-12-04 00:18:05 +00:00
case AccountState : : Unchecked : {
m_accountToUse - > refresh ( ) ;
2023-05-07 07:55:56 +00:00
[[fallthrough]] ;
2021-12-04 00:18:05 +00:00
}
case AccountState : : Working : {
// refresh is in progress, we need to wait for it to finish to proceed.
ProgressDialog progDialog ( m_parentWidget ) ;
if ( m_online )
2021-11-10 02:02:51 +00:00
{
2021-12-04 00:18:05 +00:00
progDialog . setSkipButton ( true , tr ( " Play Offline " ) ) ;
2021-11-10 02:02:51 +00:00
}
2021-12-04 00:18:05 +00:00
auto task = m_accountToUse - > currentTask ( ) ;
progDialog . execWithTask ( task . get ( ) ) ;
continue ;
}
case AccountState : : Queued : {
2023-05-07 07:55:56 +00:00
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
qWarning ( ) < < " AccountState::Queued is not implemented " ;
2021-12-04 00:18:05 +00:00
return ;
2021-11-10 02:02:51 +00:00
}
2021-12-04 00:18:05 +00:00
case AccountState : : Expired : {
auto errorString = tr ( " The account has expired and needs to be logged into manually again. " ) ;
2021-08-27 20:35:17 +00:00
QMessageBox : : warning (
2021-11-10 02:02:51 +00:00
m_parentWidget ,
2021-12-04 00:18:05 +00:00
tr ( " Account refresh failed " ) ,
2021-08-27 20:35:17 +00:00
errorString ,
QMessageBox : : StandardButton : : Ok ,
QMessageBox : : StandardButton : : Ok
) ;
emitFailed ( errorString ) ;
2021-07-26 19:44:11 +00:00
return ;
2018-07-15 12:51:05 +00:00
}
2022-02-18 11:26:52 +00:00
case AccountState : : Disabled : {
2022-02-18 18:18:29 +00:00
auto errorString = tr ( " The launcher's client identification has changed. Please remove this account and add it again. " ) ;
2022-02-18 11:26:52 +00:00
QMessageBox : : warning (
m_parentWidget ,
tr ( " Client identification changed " ) ,
errorString ,
QMessageBox : : StandardButton : : Ok ,
QMessageBox : : StandardButton : : Ok
) ;
emitFailed ( errorString ) ;
return ;
}
2021-12-04 00:18:05 +00:00
case AccountState : : Gone : {
2021-08-29 17:58:35 +00:00
auto errorString = tr ( " The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to. " ) ;
QMessageBox : : warning (
2021-11-10 02:02:51 +00:00
m_parentWidget ,
2021-08-29 17:58:35 +00:00
tr ( " Account gone " ) ,
errorString ,
QMessageBox : : StandardButton : : Ok ,
QMessageBox : : StandardButton : : Ok
) ;
emitFailed ( errorString ) ;
return ;
}
2023-05-07 07:55:56 +00:00
default : {
qWarning ( ) < < " Invalid AccountState enum " ;
}
2018-07-15 12:51:05 +00:00
}
}
emitFailed ( tr ( " Failed to launch. " ) ) ;
2015-07-04 23:54:30 +00:00
}
void LaunchController : : launchInstance ( )
{
2018-07-15 12:51:05 +00:00
Q_ASSERT_X ( m_instance ! = NULL , " launchInstance " , " instance is NULL " ) ;
Q_ASSERT_X ( m_session . get ( ) ! = nullptr , " launchInstance " , " session is NULL " ) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
if ( ! m_instance - > reloadSettings ( ) )
{
2019-09-15 03:31:13 +00:00
QMessageBox : : critical ( m_parentWidget , tr ( " Error! " ) , tr ( " Couldn't load the instance profile. " ) ) ;
2018-07-15 12:51:05 +00:00
emitFailed ( tr ( " Couldn't load the instance profile. " ) ) ;
return ;
}
2015-07-04 23:54:30 +00:00
2021-05-22 16:07:08 +00:00
m_launcher = m_instance - > createLaunchTask ( m_session , m_serverToJoin ) ;
2018-07-15 12:51:05 +00:00
if ( ! m_launcher )
{
emitFailed ( tr ( " Couldn't instantiate a launcher. " ) ) ;
return ;
}
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
auto console = qobject_cast < InstanceWindow * > ( m_parentWidget ) ;
auto showConsole = m_instance - > settings ( ) - > get ( " ShowConsole " ) . toBool ( ) ;
if ( ! console & & showConsole )
{
2021-11-20 15:22:22 +00:00
APPLICATION - > showInstanceWindow ( m_instance ) ;
2018-07-15 12:51:05 +00:00
}
connect ( m_launcher . get ( ) , & LaunchTask : : readyForLaunch , this , & LaunchController : : readyForLaunch ) ;
connect ( m_launcher . get ( ) , & LaunchTask : : succeeded , this , & LaunchController : : onSucceeded ) ;
connect ( m_launcher . get ( ) , & LaunchTask : : failed , this , & LaunchController : : onFailed ) ;
connect ( m_launcher . get ( ) , & LaunchTask : : requestProgress , this , & LaunchController : : onProgressRequested ) ;
2016-11-01 00:25:04 +00:00
2021-06-19 01:15:21 +00:00
// Prepend Online and Auth Status
QString online_mode ;
if ( m_session - > wants_online ) {
online_mode = " online " ;
2015-07-04 23:54:30 +00:00
2021-06-19 01:15:21 +00:00
// Prepend Server Status
QStringList servers = { " authserver.mojang.com " , " session.minecraft.net " , " textures.minecraft.net " , " api.mojang.com " } ;
QString resolved_servers = " " ;
QHostInfo host_info ;
for ( QString server : servers ) {
host_info = QHostInfo : : fromName ( server ) ;
resolved_servers = resolved_servers + server + " resolves to: \n [ " ;
if ( ! host_info . addresses ( ) . isEmpty ( ) ) {
for ( QHostAddress address : host_info . addresses ( ) ) {
resolved_servers = resolved_servers + address . toString ( ) ;
if ( ! host_info . addresses ( ) . endsWith ( address ) ) {
resolved_servers = resolved_servers + " , " ;
}
}
} else {
resolved_servers = resolved_servers + " N/A " ;
}
resolved_servers = resolved_servers + " ] \n \n " ;
}
2021-10-17 22:47:02 +00:00
m_launcher - > prependStep ( new TextPrint ( m_launcher . get ( ) , resolved_servers , MessageLevel : : Launcher ) ) ;
2021-06-19 01:15:21 +00:00
} else {
2022-09-15 21:32:14 +00:00
online_mode = m_demo ? " demo " : " offline " ;
2021-06-19 01:15:21 +00:00
}
2021-12-04 00:18:05 +00:00
m_launcher - > prependStep ( new TextPrint ( m_launcher . get ( ) , " Launched instance in " + online_mode + " mode \n " , MessageLevel : : Launcher ) ) ;
2021-06-19 01:15:21 +00:00
// Prepend Version
2021-10-17 22:47:02 +00:00
m_launcher - > prependStep ( new TextPrint ( m_launcher . get ( ) , BuildConfig . LAUNCHER_NAME + " version: " + BuildConfig . printableVersionString ( ) + " \n \n " , MessageLevel : : Launcher ) ) ;
2018-07-15 12:51:05 +00:00
m_launcher - > start ( ) ;
2015-07-04 23:54:30 +00:00
}
void LaunchController : : readyForLaunch ( )
{
2018-07-15 12:51:05 +00:00
if ( ! m_profiler )
{
m_launcher - > proceed ( ) ;
return ;
}
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
QString error ;
if ( ! m_profiler - > check ( & error ) )
{
m_launcher - > abort ( ) ;
2019-09-15 03:31:13 +00:00
QMessageBox : : critical ( m_parentWidget , tr ( " Error! " ) , tr ( " Couldn't start profiler: %1 " ) . arg ( error ) ) ;
emitFailed ( " Profiler startup failed! " ) ;
2018-07-15 12:51:05 +00:00
return ;
}
BaseProfiler * profilerInstance = m_profiler - > createProfiler ( m_launcher - > instance ( ) , this ) ;
2015-07-04 23:54:30 +00:00
2018-07-15 12:51:05 +00:00
connect ( profilerInstance , & BaseProfiler : : readyToLaunch , [ this ] ( const QString & message )
{
QMessageBox msg ;
msg . setText ( tr ( " The game launch is delayed until you press the "
" button. This is the right time to setup the profiler, as the "
" profiler server is running now. \n \n %1 " ) . arg ( message ) ) ;
2019-09-15 03:31:13 +00:00
msg . setWindowTitle ( tr ( " Waiting. " ) ) ;
2018-07-15 12:51:05 +00:00
msg . setIcon ( QMessageBox : : Information ) ;
msg . addButton ( tr ( " Launch " ) , QMessageBox : : AcceptRole ) ;
msg . setModal ( true ) ;
msg . exec ( ) ;
m_launcher - > proceed ( ) ;
} ) ;
connect ( profilerInstance , & BaseProfiler : : abortLaunch , [ this ] ( const QString & message )
{
QMessageBox msg ;
msg . setText ( tr ( " Couldn't start the profiler: %1 " ) . arg ( message ) ) ;
msg . setWindowTitle ( tr ( " Error " ) ) ;
msg . setIcon ( QMessageBox : : Critical ) ;
msg . addButton ( QMessageBox : : Ok ) ;
msg . setModal ( true ) ;
msg . exec ( ) ;
m_launcher - > abort ( ) ;
2019-09-15 03:31:13 +00:00
emitFailed ( " Profiler startup failed! " ) ;
2018-07-15 12:51:05 +00:00
} ) ;
profilerInstance - > beginProfiling ( m_launcher ) ;
2015-07-04 23:54:30 +00:00
}
2016-11-01 00:25:04 +00:00
void LaunchController : : onSucceeded ( )
{
2018-07-15 12:51:05 +00:00
emitSucceeded ( ) ;
2016-11-01 00:25:04 +00:00
}
void LaunchController : : onFailed ( QString reason )
{
2018-07-15 12:51:05 +00:00
if ( m_instance - > settings ( ) - > get ( " ShowConsoleOnError " ) . toBool ( ) )
{
2021-11-20 15:22:22 +00:00
APPLICATION - > showInstanceWindow ( m_instance , " console " ) ;
2018-07-15 12:51:05 +00:00
}
emitFailed ( reason ) ;
2016-11-01 00:25:04 +00:00
}
void LaunchController : : onProgressRequested ( Task * task )
{
2018-07-15 12:51:05 +00:00
ProgressDialog progDialog ( m_parentWidget ) ;
progDialog . setSkipButton ( true , tr ( " Abort " ) ) ;
m_launcher - > proceed ( ) ;
progDialog . execWithTask ( task ) ;
2016-11-01 00:25:04 +00:00
}
2016-11-26 17:06:08 +00:00
bool LaunchController : : abort ( )
{
2018-07-15 12:51:05 +00:00
if ( ! m_launcher )
{
return true ;
}
if ( ! m_launcher - > canAbort ( ) )
{
return false ;
}
auto response = CustomMessageBox : : selectable (
m_parentWidget , tr ( " Kill Minecraft? " ) ,
tr ( " This can cause the instance to get corrupted and should only be used if Minecraft "
" is frozen for some reason " ) ,
QMessageBox : : Question , QMessageBox : : Yes | QMessageBox : : No , QMessageBox : : Yes ) - > exec ( ) ;
if ( response = = QMessageBox : : Yes )
{
return m_launcher - > abort ( ) ;
}
return false ;
2016-11-26 17:06:08 +00:00
}