2026-07-14 01:37:06 +00:00
#include <QApplication>
#include <QCoreApplication>
#include <QDebug>
2026-09-08 17:08:58 +08:00
#include <QDir>
#include <QFileInfo>
2026-07-14 01:37:06 +00:00
#include <QMessageBox>
#include <QProcess>
#include <QProgressDialog>
#include <QTranslator>
2026-09-08 17:08:58 +08:00
#include <QUuid>
2026-07-14 01:37:06 +00:00
#include "UpdateLogic.h"
2026-09-08 17:08:58 +08:00
#include "../Common/ConfigHelper.h"
#include "../Common/TicketHelper.h"
namespace {
QString configValue ( const QString & key , const QString & fallback = QString ())
{
const QString value = ConfigHelper :: instance (). getValue ( QString (), key ). trimmed ();
return value . isEmpty () ? fallback : value ;
}
QString productCode ()
{
return configValue ( QStringLiteral ( "product_code" ),
configValue ( QStringLiteral ( "app_id" )));
}
QString ensureDeviceId ()
{
ConfigHelper & config = ConfigHelper :: instance ();
QString deviceId = config . getValue ( QStringLiteral ( "Update" ), QStringLiteral ( "device_id" )). trimmed ();
if ( ! deviceId . isEmpty ())
return deviceId ;
deviceId = config . getValue ( QStringLiteral ( "Device" ), QStringLiteral ( "installation_id" )). trimmed ();
if ( deviceId . isEmpty ())
deviceId = QUuid :: createUuid (). toString ( QUuid :: WithoutBraces );
config . setValue ( QStringLiteral ( "Device" ), QStringLiteral ( "installation_id" ), deviceId );
config . setValue ( QStringLiteral ( "Update" ), QStringLiteral ( "device_id" ), deviceId );
return deviceId ;
}
QString authFailureMessage ( const QJsonObject & response , const QString & fallback )
{
const QString msg = response . value ( QStringLiteral ( "msg" )). toString ();
if ( ! msg . isEmpty ())
return msg ;
const QJsonValue detail = response . value ( QStringLiteral ( "detail" ));
if ( detail . isObject ()) {
const QJsonObject object = detail . toObject ();
const QString detailMsg = object . value ( QStringLiteral ( "msg" )). toString ();
if ( ! detailMsg . isEmpty ())
return detailMsg ;
const QString error = object . value ( QStringLiteral ( "error" )). toString ();
if ( ! error . isEmpty ())
return error ;
}
const QString detailText = detail . toString ();
return detailText . isEmpty () ? fallback : detailText ;
}
} // namespace
2026-07-14 01:37:06 +00:00
int main ( int argc , char * argv [])
{
QApplication app ( argc , argv );
2026-09-08 17:08:58 +08:00
QApplication :: setApplicationName ( "SimCAE Launcher" );
2026-07-14 01:37:06 +00:00
QTranslator translator ;
2026-09-08 17:08:58 +08:00
if ( translator . load ( QStringLiteral ( ":/i18n/update-client_zh_CN.qm" )))
2026-07-14 01:37:06 +00:00
app . installTranslator ( & translator );
const int elevatedWriteExitCode = ConfigHelper :: runElevatedWriteCommandIfRequested ();
if ( elevatedWriteExitCode >= 0 )
return elevatedWriteExitCode ;
QProgressDialog progress ( QCoreApplication :: translate ( "Launcher" , "Checking for software updates..." ), QString (), 0 , 0 );
2026-09-08 17:08:58 +08:00
progress . setWindowTitle ( QCoreApplication :: translate ( "Launcher" , "SimCAE Launcher" ));
progress . setCancelButton ( nullptr );
progress . setWindowModality ( Qt :: ApplicationModal );
progress . setMinimumDuration ( 0 );
progress . setAutoClose ( false );
progress . show ();
QApplication :: processEvents ();
2026-07-16 08:14:51 +00:00
ConfigHelper & config = ConfigHelper :: instance ();
2026-09-08 17:08:58 +08:00
const QString appDir = QApplication :: applicationDirPath ();
progress . setLabelText ( QCoreApplication :: translate ( "Launcher" , "Checking authorized updates..." ));
QApplication :: processEvents ();
UpdateLogic logic ;
logic . checkUpdate ();
const bool needUpdate = logic . getNeedUpdate ();
const bool networkOk = logic . isNetworkOk ();
const QString latestVer = logic . getLatestVersion ();
2026-07-14 01:37:06 +00:00
const QJsonObject response = logic . getCheckResult ();
2026-09-08 17:08:58 +08:00
const QString releaseId = response . value ( QStringLiteral ( "release_id" )). toString (
response . value ( QStringLiteral ( "id" )). toString ());
const QString appId = logic . getProductCode ();
2026-07-14 01:37:06 +00:00
const QString channel = logic . getChannel ();
2026-09-08 17:08:58 +08:00
const QString launchToken = config . getValue ( QStringLiteral ( "App" ), QStringLiteral ( "launch_token" ));
const QString currentVersion = config . getValue ( QStringLiteral ( "App" ), QStringLiteral ( "current_version" ));
const QString deviceId = ensureDeviceId ();
2026-07-14 01:37:06 +00:00
const auto configuredName = [ & ]( const QString & key , const QString & fallback ) {
2026-07-14 08:39:41 +00:00
return ConfigHelper :: executableNameForCurrentPlatform (
2026-09-08 17:08:58 +08:00
config . getValue ( QStringLiteral ( "Runtime" ), key ), fallback );
2026-07-14 08:39:41 +00:00
};
2026-09-08 17:08:58 +08:00
const QString mainExecutable = configuredName ( QStringLiteral ( "main_executable" ), QStringLiteral ( "MainApp" ));
const QString updaterExecutable = configuredName ( QStringLiteral ( "updater_executable" ), QStringLiteral ( "Updater" ));
2026-07-14 01:37:06 +00:00
const QString mainAppPath = QDir ( appDir ). filePath ( mainExecutable );
const QString updaterPath = QDir ( appDir ). filePath ( updaterExecutable );
2026-09-08 17:08:58 +08:00
2026-07-21 03:02:42 +00:00
QString mainStartupError ;
2026-07-16 08:14:51 +00:00
const auto startMainApp = [ & ]() {
2026-07-21 03:02:42 +00:00
mainStartupError . clear ();
if ( ! QFileInfo :: exists ( mainAppPath )) {
mainStartupError = QCoreApplication :: translate (
"Launcher" ,
"Cannot start the main application because the executable file does not exist. \n Executable: %1 \n Check main_executable and install_root in the generated client configuration." )
. arg ( mainAppPath );
return false ;
}
2026-09-08 17:08:58 +08:00
2026-07-16 08:14:51 +00:00
QString ticketPath ;
2026-07-21 03:02:42 +00:00
QString ticketError ;
2026-09-08 17:08:58 +08:00
if ( ! TicketHelper :: createTicket ( appId , deviceId , currentVersion ,
2026-07-21 03:02:42 +00:00
launchToken , & ticketPath , & ticketError )) {
qDebug () << "Cannot create launch ticket:" << ticketError ;
mainStartupError = QCoreApplication :: translate (
"Launcher" ,
"Cannot start the main application because the one-time launch ticket could not be created. \n Executable: %1 \n Details: %2" )
. arg ( mainAppPath , ticketError );
return false ;
}
2026-09-08 17:08:58 +08:00
2026-07-21 03:02:42 +00:00
const bool started = QProcess :: startDetached ( mainAppPath ,
2026-09-08 17:08:58 +08:00
QStringList { QStringLiteral ( "--ticket-file=%1" ). arg ( ticketPath )});
2026-07-21 03:02:42 +00:00
if ( ! started ) {
QFile :: remove ( ticketPath );
mainStartupError = QCoreApplication :: translate (
"Launcher" ,
"Cannot start the main application process. \n Executable: %1 \n Ticket file: %2 \n Check file permissions, dependent DLLs/shared libraries, and whether the executable can run independently." )
. arg ( mainAppPath , ticketPath );
}
return started ;
};
2026-09-08 17:08:58 +08:00
if ( logic . lastStatusCode () == 401 || logic . lastStatusCode () == 403 ) {
2026-07-20 06:43:46 +00:00
progress . close ();
2026-07-14 01:37:06 +00:00
QMessageBox :: critical ( nullptr ,
2026-09-08 17:08:58 +08:00
QCoreApplication :: translate ( "Launcher" , "Update Client Rejected" ),
authFailureMessage ( response ,
QCoreApplication :: translate ( "Launcher" , "The update client token is missing or invalid. Please download a valid package from the customer portal." )));
2026-07-14 01:37:06 +00:00
return - 1 ;
2026-09-08 17:08:58 +08:00
} else if ( ! networkOk ) {
progress . close ();
QMessageBox :: warning ( nullptr ,
QCoreApplication :: translate ( "Launcher" , "Update Server Unavailable" ),
QCoreApplication :: translate ( "Launcher" , "Cannot connect to the update server. The installed application will be started without downloading an update." ));
progress . show ();
2026-07-20 06:43:46 +00:00
}
2026-07-14 01:37:06 +00:00
if ( networkOk && needUpdate )
{
progress . close ();
2026-09-08 17:08:58 +08:00
const QString prompt = QCoreApplication :: translate (
"Launcher" ,
"Version %1 is available. Update now?" ). arg ( latestVer );
const bool accepted = QMessageBox :: question ( nullptr ,
QCoreApplication :: translate ( "Launcher" , "New Version Available" ),
prompt ,
QMessageBox :: Yes | QMessageBox :: No ,
QMessageBox :: No ) == QMessageBox :: Yes ;
2026-07-21 03:02:42 +00:00
if ( ! accepted ) {
2026-07-14 01:37:06 +00:00
if ( ! startMainApp ()) {
QMessageBox :: critical ( nullptr ,
QCoreApplication :: translate ( "Launcher" , "Startup Failed" ),
2026-07-21 03:02:42 +00:00
mainStartupError . isEmpty ()
? QCoreApplication :: translate ( "Launcher" , "Cannot start the main application: %1" ). arg ( mainAppPath )
: mainStartupError );
2026-07-14 01:37:06 +00:00
return - 1 ;
}
2026-07-21 03:02:42 +00:00
return 0 ;
}
2026-09-08 17:08:58 +08:00
const QStringList updaterArgs {
appId ,
channel ,
latestVer ,
QStringLiteral ( "0" ),
QStringLiteral ( "--release-id=%1" ). arg ( releaseId )
};
2026-07-21 03:02:42 +00:00
if ( ! QFileInfo :: exists ( updaterPath ))
{
QMessageBox :: critical ( nullptr ,
QCoreApplication :: translate ( "Launcher" , "Updater Startup Failed" ),
QCoreApplication :: translate (
"Launcher" ,
"Cannot start the updater because the executable file does not exist. \n Executable: %1 \n Check updater_executable and install_root in the generated client configuration." )
. arg ( updaterPath ));
return - 1 ;
}
2026-07-14 01:37:06 +00:00
if ( ! QProcess :: startDetached ( updaterPath , updaterArgs ))
{
QMessageBox :: critical ( nullptr ,
QCoreApplication :: translate ( "Launcher" , "Updater Startup Failed" ),
2026-07-21 03:02:42 +00:00
QCoreApplication :: translate (
"Launcher" ,
"Cannot start the updater process. \n Executable: %1 \n Arguments: %2 \n Check file permissions, dependent DLLs/shared libraries, and whether the updater can run independently." )
. arg ( updaterPath , updaterArgs . join ( QStringLiteral ( " " ))));
2026-07-14 01:37:06 +00:00
return - 1 ;
2026-07-21 03:02:42 +00:00
}
2026-07-14 01:37:06 +00:00
return 0 ;
}
progress . setLabelText ( networkOk
? QCoreApplication :: translate ( "Launcher" , "The application is up to date. Starting..." )
2026-09-08 17:08:58 +08:00
: QCoreApplication :: translate ( "Launcher" , "Offline startup. Starting..." ));
QApplication :: processEvents ();
2026-07-21 03:02:42 +00:00
if ( ! startMainApp ())
{
progress . close ();
2026-07-14 01:37:06 +00:00
QMessageBox :: critical ( nullptr ,
QCoreApplication :: translate ( "Launcher" , "Startup Failed" ),
2026-07-21 03:02:42 +00:00
mainStartupError . isEmpty ()
? QCoreApplication :: translate ( "Launcher" , "Cannot start the main application: %1" ). arg ( mainAppPath )
: mainStartupError );
return - 1 ;
}
2026-09-08 17:08:58 +08:00
progress . close ();
return 0 ;
}