1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-20 11:47:46 +00:00

Separate main launcher core and launcher window, add basic command line support

This commit is contained in:
redstrate 2021-11-23 15:34:23 -05:00
parent 6d03631b66
commit 9bd6e9c977
16 changed files with 364 additions and 328 deletions

View file

@ -12,12 +12,16 @@ add_subdirectory(external)
add_executable(xivlauncher
src/main.cpp
src/xivlauncher.cpp
src/launchercore.cpp
src/sapphirelauncher.cpp
src/squareboot.cpp
src/squarelauncher.cpp
src/settingswindow.cpp
src/blowfish.cpp src/assetupdater.cpp src/assetupdater.h)
src/blowfish.cpp
src/assetupdater.cpp
src/assetupdater.h
src/launcherwindow.cpp
src/launcherwindow.h)
target_link_libraries(xivlauncher Qt5::Core Qt5::Widgets Qt5::Network qt5keychain QuaZip)

View file

@ -6,7 +6,7 @@
#include <quazip/JlCompress.h>
#include "xivlauncher.h"
#include "launchercore.h"
const QString dalamudRemotePath = "https://goatcorp.github.io/dalamud-distrib/";
const QString dalamudVersion = "latest";
@ -14,19 +14,19 @@ const QString dalamudVersion = "latest";
const QString nativeLauncherRemotePath = "https://github.com/redstrate/nativelauncher/releases/download/";
const QString nativeLauncherVersion = "v1.0.0";
AssetUpdater::AssetUpdater(LauncherWindow &launcher) : launcher(launcher) {
AssetUpdater::AssetUpdater(LauncherCore &launcher) : launcher(launcher) {
connect(launcher.mgr, &QNetworkAccessManager::finished, this, &AssetUpdater::finishDownload);
launcher.mgr->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
}
void AssetUpdater::update() {
void AssetUpdater::update(const ProfileSettings& profile) {
QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
const bool hasDalamud = QFile::exists(dataDir + "/NativeLauncher.exe") && QFile::exists(dataDir + "/Dalamud");
// first we determine if we need dalamud
const bool needsDalamud = launcher.currentProfile().enableDalamud && !hasDalamud;
const bool needsDalamud = profile.enableDalamud && !hasDalamud;
if(needsDalamud) {
// download nativelauncher release (needed to launch the game with fixed ACLs)
{

View file

@ -3,15 +3,16 @@
#include <QObject>
#include <QTemporaryDir>
class LauncherWindow;
class LauncherCore;
class QNetworkReply;
struct ProfileSettings;
class AssetUpdater : public QObject {
Q_OBJECT
public:
AssetUpdater(LauncherWindow& launcher);
AssetUpdater(LauncherCore& launcher);
void update();
void update(const ProfileSettings& profile);
void finishDownload(QNetworkReply* reply);
void beginInstall();
@ -19,7 +20,7 @@ signals:
void finishedUpdating();
private:
LauncherWindow& launcher;
LauncherCore& launcher;
QTemporaryDir tempDir;
};

View file

@ -27,7 +27,7 @@
#include <windows.h>
#endif
#include "xivlauncher.h"
#include "launchercore.h"
#include "sapphirelauncher.h"
#include "squarelauncher.h"
#include "squareboot.h"
@ -35,7 +35,7 @@
#include "blowfish.h"
#include "assetupdater.h"
void LauncherWindow::setSSL(QNetworkRequest& request) {
void LauncherCore::setSSL(QNetworkRequest& request) {
QSslConfiguration config;
config.setProtocol(QSsl::AnyProtocol);
config.setPeerVerifyMode(QSslSocket::VerifyNone);
@ -43,7 +43,7 @@ void LauncherWindow::setSSL(QNetworkRequest& request) {
request.setSslConfiguration(config);
}
void LauncherWindow::buildRequest(QNetworkRequest& request) {
void LauncherCore::buildRequest(QNetworkRequest& request) {
setSSL(request);
request.setHeader(QNetworkRequest::UserAgentHeader,
QString("SQEXAuthor/2.0.0(Windows 6.2; ja-jp; %1)").arg(QString(QSysInfo::bootUniqueId())));
@ -106,20 +106,20 @@ QString encryptGameArg(QString arg) {
return QString("//**sqex0003%1%2**//").arg(base64, QString(checksum));
}
void LauncherWindow::launchGame(const LoginAuth auth) {
void LauncherCore::launchGame(const ProfileSettings& profile, const LoginAuth auth) {
QList<QString> arguments;
QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if(currentProfile().enableDalamud) {
if(profile.enableDalamud) {
arguments.push_back(dataDir + "/NativeLauncher.exe");
}
// now for the actual game...
if(currentProfile().useDX9) {
arguments.push_back(currentProfile().gamePath + "\\game\\ffxiv.exe");
if(profile.useDX9) {
arguments.push_back(profile.gamePath + "\\game\\ffxiv.exe");
} else {
arguments.push_back(currentProfile().gamePath + "\\game\\ffxiv_dx11.exe");
arguments.push_back(profile.gamePath + "\\game\\ffxiv_dx11.exe");
}
struct Argument {
@ -133,8 +133,8 @@ void LauncherWindow::launchGame(const LoginAuth auth) {
gameArgs.push_back({"DEV.MaxEntitledExpansionID", QString::number(auth.maxExpansion)});
gameArgs.push_back({"DEV.TestSID", auth.SID});
gameArgs.push_back({"SYS.Region", QString::number(auth.region)});
gameArgs.push_back({"language", QString::number(currentProfile().language)});
gameArgs.push_back({"ver", currentProfile().gameVersion});
gameArgs.push_back({"language", QString::number(profile.language)});
gameArgs.push_back({"ver", profile.gameVersion});
if(!auth.lobbyhost.isEmpty()) {
gameArgs.push_back({"DEV.GMServerHost", auth.frontierHost});
@ -146,8 +146,8 @@ void LauncherWindow::launchGame(const LoginAuth auth) {
auto gameProcess = new QProcess(this);
if(currentProfile().enableDalamud) {
connect(gameProcess, &QProcess::readyReadStandardOutput, [this, gameProcess] {
if(profile.enableDalamud) {
connect(gameProcess, &QProcess::readyReadStandardOutput, [this, gameProcess, profile] {
QString output = gameProcess->readAllStandardOutput();
auto dalamudProcess = new QProcess();
@ -162,11 +162,11 @@ void LauncherWindow::launchGame(const LoginAuth auth) {
QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
dalamudProcess->start(currentProfile().winePath, {dataDir + "/Dalamud/" + "Dalamud.Injector.exe", output});
dalamudProcess->start(profile.winePath, {dataDir + "/Dalamud/" + "Dalamud.Injector.exe", output});
});
}
if(currentProfile().encryptArguments) {
if(profile.encryptArguments) {
QString argJoined;
for(auto arg : gameArgs) {
argJoined += QString(" /%1 =%2").arg(arg.key, arg.value);
@ -174,47 +174,46 @@ void LauncherWindow::launchGame(const LoginAuth auth) {
auto earg = encryptGameArg(argJoined);
arguments.append(earg);
launchExecutable(gameProcess, arguments);
launchExecutable(profile, gameProcess, arguments);
} else {
for(auto arg : gameArgs) {
arguments.push_back(QString(" %1=%2").arg(arg.key, arg.value));
}
launchExecutable(gameProcess, arguments);
launchExecutable(profile, gameProcess, arguments);
}
}
void LauncherWindow::launchExecutable(const QStringList args) {
void LauncherCore::launchExecutable(const ProfileSettings& profile, const QStringList args) {
auto process = new QProcess(this);
launchExecutable(process, args);
launchExecutable(profile, process, args);
}
void LauncherWindow::launchExecutable(QProcess* process, const QStringList args) {
void LauncherCore::launchExecutable(const ProfileSettings& profile, QProcess* process, const QStringList args) {
QList<QString> arguments;
QStringList env = QProcess::systemEnvironment();
#if defined(Q_OS_LINUX)
if(currentProfile().useGamescope) {
if(profile.useGamescope) {
arguments.push_back("gamescope");
arguments.push_back("-f");
arguments.push_back("-b");
}
if(currentProfile().useGamemode)
if(profile.useGamemode)
arguments.push_back("gamemoderun");
if(currentProfile().useEsync) {
if(profile.useEsync)
env << "WINEESYNC=1";
}
#endif
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
env << "WINEPREFIX=" + currentProfile().winePrefixPath;
env << "WINEPREFIX=" + profile.winePrefixPath;
if(currentProfile().enableDXVKhud)
if(profile.enableDXVKhud)
env << "DXVK_HUD=full";
arguments.push_back(currentProfile().winePath);
arguments.push_back(profile.winePath);
#endif
arguments.append(args);
@ -222,20 +221,20 @@ void LauncherWindow::launchExecutable(QProcess* process, const QStringList args)
auto executable = arguments[0];
arguments.removeFirst();
process->setWorkingDirectory(currentProfile().gamePath + "/game/");
process->setWorkingDirectory(profile.gamePath + "/game/");
process->setEnvironment(env);
process->start(executable, arguments);
}
QString LauncherWindow::readVersion(QString path) {
QString LauncherCore::readVersion(QString path) {
QFile file(path);
file.open(QFile::OpenModeFlag::ReadOnly);
return file.readAll();
}
void LauncherWindow::readInitialInformation() {
void LauncherCore::readInitialInformation() {
defaultProfileIndex = settings.value("defaultProfile", 0).toInt();
auto profiles = settings.childGroups();
@ -312,7 +311,7 @@ void LauncherWindow::readInitialInformation() {
readGameVersion();
}
void LauncherWindow::readWineInfo(ProfileSettings& profile) {
void LauncherCore::readWineInfo(ProfileSettings& profile) {
#if defined(Q_OS_MAC)
switch(profile.wineVersion) {
case 0: // system wine
@ -339,15 +338,14 @@ void LauncherWindow::readWineInfo(ProfileSettings& profile) {
#endif
}
void LauncherWindow::readGameVersion() {
void LauncherCore::readGameVersion() {
for(auto& profile : profileSettings) {
profile.bootVersion = readVersion(profile.gamePath + "/boot/ffxivboot.ver");
profile.gameVersion = readVersion(profile.gamePath + "/game/ffxivgame.ver");
}
}
LauncherWindow::LauncherWindow(QWidget* parent) :
QMainWindow(parent), settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::applicationName()) {
LauncherCore::LauncherCore() : settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::applicationName()) {
mgr = new QNetworkAccessManager();
sapphireLauncher = new SapphireLauncher(*this);
squareLauncher = new SquareLauncher(*this);
@ -355,152 +353,17 @@ LauncherWindow::LauncherWindow(QWidget* parent) :
assetUpdater = new AssetUpdater(*this);
readInitialInformation();
QMenu* fileMenu = menuBar()->addMenu("File");
// sorry linux users, for some reason my global menu does not like qt6 apps right now
#if defined(Q_OS_LINUX)
menuBar()->setNativeMenuBar(false);
#endif
QAction* settingsAction = fileMenu->addAction("Settings...");
connect(settingsAction, &QAction::triggered, [=] {
auto window = new SettingsWindow(*this);
connect(this, &LauncherWindow::settingsChanged, window, &SettingsWindow::reloadControls);
window->show();
});
QMenu* toolsMenu = menuBar()->addMenu("Tools");
QAction* launchOfficial = toolsMenu->addAction("Launch Official Client...");
connect(launchOfficial, &QAction::triggered, [=] {
launchExecutable({currentProfile().gamePath + "/boot/ffxivboot64.exe"});
});
QAction* launchSysInfo = toolsMenu->addAction("Launch System Info...");
connect(launchSysInfo, &QAction::triggered, [=] {
launchExecutable({currentProfile().gamePath + "/boot/ffxivsysinfo64.exe"});
});
QAction* launchCfgBackup = toolsMenu->addAction("Launch Config Backup...");
connect(launchCfgBackup, &QAction::triggered, [=] {
launchExecutable({currentProfile().gamePath + "/boot/ffxivconfig64.exe"});
});
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QMenu* wineMenu = toolsMenu->addMenu("Wine");
QAction* wineCfg = wineMenu->addAction("winecfg");
connect(wineCfg, &QAction::triggered, [=] {
launchExecutable({"winecfg.exe"});
});
QAction* controlPanel = wineMenu->addAction("Control Panel");
connect(controlPanel, &QAction::triggered, [=] {
launchExecutable({"control.exe"});
});
#endif
auto layout = new QFormLayout();
profileSelect = new QComboBox();
connect(profileSelect, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
reloadControls();
});
layout->addRow("Profile", profileSelect);
usernameEdit = new QLineEdit();
layout->addRow("Username", usernameEdit);
rememberUsernameBox = new QCheckBox();
connect(rememberUsernameBox, &QCheckBox::stateChanged, [=](int) {
currentProfile().rememberUsername = rememberUsernameBox->isChecked();
saveSettings();
});
layout->addRow("Remember Username?", rememberUsernameBox);
passwordEdit = new QLineEdit();
passwordEdit->setEchoMode(QLineEdit::EchoMode::Password);
layout->addRow("Password", passwordEdit);
rememberPasswordBox = new QCheckBox();
connect(rememberPasswordBox, &QCheckBox::stateChanged, [=](int) {
currentProfile().rememberPassword = rememberPasswordBox->isChecked();
saveSettings();
});
layout->addRow("Remember Password?", rememberPasswordBox);
otpEdit = new QLineEdit();
layout->addRow("One-Time Password", otpEdit);
auto loginButton = new QPushButton("Login");
layout->addRow(loginButton);
registerButton = new QPushButton("Register");
layout->addRow(registerButton);
auto emptyWidget = new QWidget();
emptyWidget->setLayout(layout);
setCentralWidget(emptyWidget);
connect(assetUpdater, &AssetUpdater::finishedUpdating, [=] {
auto info = LoginInformation{usernameEdit->text(), passwordEdit->text(), otpEdit->text()};
if(currentProfile().rememberUsername) {
auto job = new QKeychain::WritePasswordJob("LauncherWindow");
job->setTextData(usernameEdit->text());
job->setKey(currentProfile().name + "-username");
job->start();
}
if(currentProfile().rememberPassword) {
auto job = new QKeychain::WritePasswordJob("LauncherWindow");
job->setTextData(passwordEdit->text());
job->setKey(currentProfile().name + "-password");
job->start();
}
if(currentProfile().isSapphire) {
sapphireLauncher->login(currentProfile().lobbyURL, info);
} else {
squareBoot->bootCheck(info);
}
});
connect(loginButton, &QPushButton::released, [=] {
// update the assets first if needed, then it calls the slot above :-)
assetUpdater->update();
});
connect(registerButton, &QPushButton::released, [=] {
if(currentProfile().isSapphire) {
auto info = LoginInformation{usernameEdit->text(), passwordEdit->text(), otpEdit->text()};
sapphireLauncher->registerAccount(currentProfile().lobbyURL, info);
}
});
reloadControls();
}
LauncherWindow::~LauncherWindow() = default;
ProfileSettings LauncherWindow::currentProfile() const {
return getProfile(profileSelect->currentIndex());
}
ProfileSettings& LauncherWindow::currentProfile() {
return getProfile(profileSelect->currentIndex());
}
ProfileSettings LauncherWindow::getProfile(int index) const {
ProfileSettings LauncherCore::getProfile(int index) const {
return profileSettings[index];
}
ProfileSettings& LauncherWindow::getProfile(int index) {
ProfileSettings& LauncherCore::getProfile(int index) {
return profileSettings[index];
}
int LauncherWindow::getProfileIndex(QString name) {
int LauncherCore::getProfileIndex(QString name) {
for(int i = 0; i < profileSettings.size(); i++) {
if(profileSettings[i].name == name)
return i;
@ -509,7 +372,7 @@ int LauncherWindow::getProfileIndex(QString name) {
return -1;
}
QList<QString> LauncherWindow::profileList() const {
QList<QString> LauncherCore::profileList() const {
QList<QString> list;
for(auto profile : profileSettings) {
list.append(profile.name);
@ -518,7 +381,7 @@ QList<QString> LauncherWindow::profileList() const {
return list;
}
int LauncherWindow::addProfile() {
int LauncherCore::addProfile() {
ProfileSettings newProfile;
newProfile.uuid = QUuid::createUuid();
newProfile.name = "New Profile";
@ -530,7 +393,7 @@ int LauncherWindow::addProfile() {
return profileSettings.size() - 1;
}
int LauncherWindow::deleteProfile(QString name) {
int LauncherCore::deleteProfile(QString name) {
int index = 0;
for(int i = 0; i < profileSettings.size(); i++) {
if(profileSettings[i].name == name)
@ -547,7 +410,7 @@ int LauncherWindow::deleteProfile(QString name) {
return index - 1;
}
void LauncherWindow::saveSettings() {
void LauncherCore::saveSettings() {
settings.setValue("defaultProfile", defaultProfileIndex);
for(int i = 0; i < profileSettings.size(); i++) {
@ -583,51 +446,3 @@ void LauncherWindow::saveSettings() {
settings.endGroup();
}
}
void LauncherWindow::reloadControls() {
if(currentlyReloadingControls)
return;
currentlyReloadingControls = true;
const int oldIndex = profileSelect->currentIndex();
profileSelect->clear();
for(const auto& profile : profileList()) {
profileSelect->addItem(profile);
}
profileSelect->setCurrentIndex(oldIndex);
if(profileSelect->currentIndex() == -1) {
profileSelect->setCurrentIndex(defaultProfileIndex);
}
rememberUsernameBox->setChecked(currentProfile().rememberUsername);
if(currentProfile().rememberUsername) {
auto job = new QKeychain::ReadPasswordJob("LauncherWindow");
job->setKey(currentProfile().name + "-username");
job->start();
connect(job, &QKeychain::ReadPasswordJob::finished, [=](QKeychain::Job* j) {
usernameEdit->setText(job->textData());
});
}
rememberPasswordBox->setChecked(currentProfile().rememberPassword);
if(currentProfile().rememberPassword) {
auto job = new QKeychain::ReadPasswordJob("LauncherWindow");
job->setKey(currentProfile().name + "-password");
job->start();
connect(job, &QKeychain::ReadPasswordJob::finished, [=](QKeychain::Job* j) {
passwordEdit->setText(job->textData());
});
}
registerButton->setEnabled(currentProfile().isSapphire);
otpEdit->setEnabled(!currentProfile().isSapphire);
currentlyReloadingControls = false;
}

View file

@ -4,9 +4,6 @@
#include <QNetworkAccessManager>
#include <QFuture>
#include <QSettings>
#include <QComboBox>
#include <QCheckBox>
#include <QPushButton>
#include <QUuid>
#include <QProcess>
@ -45,6 +42,8 @@ struct ProfileSettings {
};
struct LoginInformation {
ProfileSettings* settings = nullptr;
QString username, password, oneTimePassword;
};
@ -57,18 +56,13 @@ struct LoginAuth {
QString lobbyhost, frontierHost;
};
class LauncherWindow : public QMainWindow {
class LauncherCore : public QObject {
Q_OBJECT
public:
explicit LauncherWindow(QWidget* parent = nullptr);
~LauncherWindow() override;
LauncherCore();
QNetworkAccessManager* mgr;
ProfileSettings currentProfile() const;
ProfileSettings& currentProfile();
ProfileSettings getProfile(int index) const;
ProfileSettings& getProfile(int index);
@ -77,9 +71,9 @@ public:
int addProfile();
int deleteProfile(QString name);
void launchGame(const LoginAuth auth);
void launchExecutable(QStringList args);
void launchExecutable(QProcess* process, QStringList args);
void launchGame(const ProfileSettings& settings, const LoginAuth auth);
void launchExecutable(const ProfileSettings& settings, QStringList args);
void launchExecutable(const ProfileSettings& settings, QProcess* process, QStringList args);
void buildRequest(QNetworkRequest& request);
void setSSL(QNetworkRequest& request);
QString readVersion(QString path);
@ -90,26 +84,15 @@ public:
QSettings settings;
public slots:
void reloadControls();
signals:
void settingsChanged();
private:
bool currentlyReloadingControls = false;
SapphireLauncher* sapphireLauncher;
SquareBoot* squareBoot;
SquareLauncher* squareLauncher;
AssetUpdater* assetUpdater;
QComboBox* profileSelect;
QLineEdit* usernameEdit, *passwordEdit;
QLineEdit* otpEdit;
QCheckBox* rememberUsernameBox, *rememberPasswordBox;
QPushButton* registerButton;
QVector<ProfileSettings> profileSettings;
int defaultProfileIndex = 0;
signals:
void settingsChanged();
private:
QVector<ProfileSettings> profileSettings;
};

192
src/launcherwindow.cpp Normal file
View file

@ -0,0 +1,192 @@
#include "launcherwindow.h"
#include <QMenuBar>
#include <keychain.h>
#include <QFormLayout>
#include "settingswindow.h"
#include "squareboot.h"
#include "squarelauncher.h"
#include "sapphirelauncher.h"
#include "assetupdater.h"
LauncherWindow::LauncherWindow(LauncherCore& core, QWidget* parent) : QMainWindow(parent), core(core) {
connect(&core, &LauncherCore::settingsChanged, this, &LauncherWindow::reloadControls);
QMenu* fileMenu = menuBar()->addMenu("File");
QAction* settingsAction = fileMenu->addAction("Settings...");
connect(settingsAction, &QAction::triggered, [=] {
auto window = new SettingsWindow(*this, this->core);
connect(&this->core, &LauncherCore::settingsChanged, window, &SettingsWindow::reloadControls);
window->show();
});
QMenu* toolsMenu = menuBar()->addMenu("Tools");
QAction* launchOfficial = toolsMenu->addAction("Launch Official Client...");
connect(launchOfficial, &QAction::triggered, [=] {
this->core.launchExecutable(currentProfile(), {currentProfile().gamePath + "/boot/ffxivboot64.exe"});
});
QAction* launchSysInfo = toolsMenu->addAction("Launch System Info...");
connect(launchSysInfo, &QAction::triggered, [=] {
this->core.launchExecutable(currentProfile(), {currentProfile().gamePath + "/boot/ffxivsysinfo64.exe"});
});
QAction* launchCfgBackup = toolsMenu->addAction("Launch Config Backup...");
connect(launchCfgBackup, &QAction::triggered, [=] {
this->core.launchExecutable(currentProfile(), {currentProfile().gamePath + "/boot/ffxivconfig64.exe"});
});
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QMenu* wineMenu = toolsMenu->addMenu("Wine");
QAction* wineCfg = wineMenu->addAction("winecfg");
connect(wineCfg, &QAction::triggered, [=] {
this->core.launchExecutable(currentProfile(), {"winecfg.exe"});
});
QAction* controlPanel = wineMenu->addAction("Control Panel");
connect(controlPanel, &QAction::triggered, [=] {
this->core.launchExecutable(currentProfile(), {"control.exe"});
});
#endif
auto layout = new QFormLayout();
profileSelect = new QComboBox();
connect(profileSelect, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
reloadControls();
});
layout->addRow("Profile", profileSelect);
usernameEdit = new QLineEdit();
layout->addRow("Username", usernameEdit);
rememberUsernameBox = new QCheckBox();
connect(rememberUsernameBox, &QCheckBox::stateChanged, [=](int) {
currentProfile().rememberUsername = rememberUsernameBox->isChecked();
this->core.saveSettings();
});
layout->addRow("Remember Username?", rememberUsernameBox);
passwordEdit = new QLineEdit();
passwordEdit->setEchoMode(QLineEdit::EchoMode::Password);
layout->addRow("Password", passwordEdit);
rememberPasswordBox = new QCheckBox();
connect(rememberPasswordBox, &QCheckBox::stateChanged, [=](int) {
currentProfile().rememberPassword = rememberPasswordBox->isChecked();
this->core.saveSettings();
});
layout->addRow("Remember Password?", rememberPasswordBox);
otpEdit = new QLineEdit();
layout->addRow("One-Time Password", otpEdit);
auto loginButton = new QPushButton("Login");
layout->addRow(loginButton);
registerButton = new QPushButton("Register");
layout->addRow(registerButton);
auto emptyWidget = new QWidget();
emptyWidget->setLayout(layout);
setCentralWidget(emptyWidget);
connect(core.assetUpdater, &AssetUpdater::finishedUpdating, [=] {
auto info = LoginInformation{&currentProfile(), usernameEdit->text(), passwordEdit->text(), otpEdit->text()};
if(currentProfile().rememberUsername) {
auto job = new QKeychain::WritePasswordJob("LauncherWindow");
job->setTextData(usernameEdit->text());
job->setKey(currentProfile().name + "-username");
job->start();
}
if(currentProfile().rememberPassword) {
auto job = new QKeychain::WritePasswordJob("LauncherWindow");
job->setTextData(passwordEdit->text());
job->setKey(currentProfile().name + "-password");
job->start();
}
if(currentProfile().isSapphire) {
this->core.sapphireLauncher->login(currentProfile().lobbyURL, info);
} else {
this->core.squareBoot->bootCheck(info);
}
});
connect(loginButton, &QPushButton::released, [=] {
// update the assets first if needed, then it calls the slot above :-)
this->core.assetUpdater->update(currentProfile());
});
connect(registerButton, &QPushButton::released, [=] {
if(currentProfile().isSapphire) {
auto info = LoginInformation{&currentProfile(), usernameEdit->text(), passwordEdit->text(), otpEdit->text()};
this->core.sapphireLauncher->registerAccount(currentProfile().lobbyURL, info);
}
});
reloadControls();
}
ProfileSettings LauncherWindow::currentProfile() const {
return core.getProfile(profileSelect->currentIndex());
}
ProfileSettings& LauncherWindow::currentProfile() {
return core.getProfile(profileSelect->currentIndex());
}
void LauncherWindow::reloadControls() {
if(currentlyReloadingControls)
return;
currentlyReloadingControls = true;
const int oldIndex = profileSelect->currentIndex();
profileSelect->clear();
for(const auto& profile : core.profileList()) {
profileSelect->addItem(profile);
}
profileSelect->setCurrentIndex(oldIndex);
if(profileSelect->currentIndex() == -1) {
profileSelect->setCurrentIndex(core.defaultProfileIndex);
}
rememberUsernameBox->setChecked(currentProfile().rememberUsername);
if(currentProfile().rememberUsername) {
auto job = new QKeychain::ReadPasswordJob("LauncherWindow");
job->setKey(currentProfile().name + "-username");
job->start();
connect(job, &QKeychain::ReadPasswordJob::finished, [=](QKeychain::Job* j) {
usernameEdit->setText(job->textData());
});
}
rememberPasswordBox->setChecked(currentProfile().rememberPassword);
if(currentProfile().rememberPassword) {
auto job = new QKeychain::ReadPasswordJob("LauncherWindow");
job->setKey(currentProfile().name + "-password");
job->start();
connect(job, &QKeychain::ReadPasswordJob::finished, [=](QKeychain::Job* j) {
passwordEdit->setText(job->textData());
});
}
registerButton->setEnabled(currentProfile().isSapphire);
otpEdit->setEnabled(!currentProfile().isSapphire);
currentlyReloadingControls = false;
}

31
src/launcherwindow.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <QMainWindow>
#include <QComboBox>
#include <QCheckBox>
#include <QPushButton>
#include "launchercore.h"
class LauncherWindow : public QMainWindow {
Q_OBJECT
public:
explicit LauncherWindow(LauncherCore& core, QWidget* parent = nullptr);
ProfileSettings currentProfile() const;
ProfileSettings& currentProfile();
public slots:
void reloadControls();
private:
LauncherCore& core;
bool currentlyReloadingControls = false;
QComboBox* profileSelect;
QLineEdit* usernameEdit, *passwordEdit;
QLineEdit* otpEdit;
QCheckBox* rememberUsernameBox, *rememberPasswordBox;
QPushButton* registerButton;
};

View file

@ -1,5 +1,8 @@
#include "xivlauncher.h"
#include "launchercore.h"
#include "launcherwindow.h"
#include <QApplication>
#include <QCommandLineParser>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
@ -13,7 +16,15 @@ int main(int argc, char* argv[]) {
QCoreApplication::setApplicationName("xivlauncher-debug");
#endif
LauncherWindow w;
LauncherCore c;
QCommandLineParser parser;
parser.setApplicationDescription("Cross-platform FFXIV Launcher");
parser.addHelpOption();
parser.addVersionOption();
parser.process(app);
LauncherWindow w(c);
w.show();
return app.exec();

View file

@ -5,7 +5,7 @@
#include <QMessageBox>
#include <QNetworkReply>
SapphireLauncher::SapphireLauncher(LauncherWindow& window) : window(window) {
SapphireLauncher::SapphireLauncher(LauncherCore& window) : window(window) {
}
@ -33,7 +33,7 @@ void SapphireLauncher::login(QString lobbyUrl, const LoginInformation& info) {
auth.frontierHost = document["frontierHost"].toString();
auth.region = 3;
window.launchGame(auth);
window.launchGame(*info.settings, auth);
} else {
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Invalid username/password.");
messageBox->show();
@ -64,6 +64,6 @@ void SapphireLauncher::registerAccount(QString lobbyUrl, const LoginInformation&
auth.frontierHost = document["frontierHost"].toString();
auth.region = 3;
window.launchGame(auth);
window.launchGame(*info.settings, auth);
});
}

View file

@ -2,15 +2,15 @@
#include <QString>
#include "xivlauncher.h"
#include "launchercore.h"
class SapphireLauncher : QObject {
public:
SapphireLauncher(LauncherWindow& window);
SapphireLauncher(LauncherCore& window);
void login(QString lobbyUrl, const LoginInformation& info);
void registerAccount(QString lobbyUrl, const LoginInformation& info);
private:
LauncherWindow& window;
LauncherCore& window;
};

View file

@ -11,9 +11,10 @@
#include <QProcess>
#include <QGridLayout>
#include "xivlauncher.h"
#include "launchercore.h"
#include "launcherwindow.h"
SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window(window), QWidget(parent) {
SettingsWindow::SettingsWindow(LauncherWindow& window, LauncherCore& core, QWidget* parent) : core(core), window(window), QWidget(parent) {
setWindowTitle("Settings");
setWindowModality(Qt::WindowModality::ApplicationModal);
@ -30,17 +31,17 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
auto addProfileButton = new QPushButton("Add Profile");
connect(addProfileButton, &QPushButton::pressed, [=] {
profileWidget->setCurrentRow(this->window.addProfile());
profileWidget->setCurrentRow(this->core.addProfile());
this->window.saveSettings();
this->core.saveSettings();
});
mainLayout->addWidget(addProfileButton, 2, 0);
deleteProfileButton = new QPushButton("Delete Profile");
connect(deleteProfileButton, &QPushButton::pressed, [=] {
profileWidget->setCurrentRow(this->window.deleteProfile(getCurrentProfile().name));
profileWidget->setCurrentRow(this->core.deleteProfile(getCurrentProfile().name));
this->window.saveSettings();
this->core.saveSettings();
});
mainLayout->addWidget(deleteProfileButton, 3, 0);
@ -49,7 +50,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
getCurrentProfile().name = nameEdit->text();
reloadControls();
this->window.saveSettings();
this->core.saveSettings();
});
mainLayout->addWidget(nameEdit, 0, 1);
@ -66,10 +67,10 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(directXCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
getCurrentProfile().useDX9 = directXCombo->currentIndex() == 1;
this->window.saveSettings();
this->core.saveSettings();
});
currentGameDirectory = new QLabel(window.currentProfile().gamePath);
currentGameDirectory = new QLabel();
currentGameDirectory->setWordWrap(true);
gameBoxLayout->addRow("Game Directory", currentGameDirectory);
@ -78,9 +79,9 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
getCurrentProfile().gamePath = QFileDialog::getExistingDirectory(this, "Open Game Directory");
this->reloadControls();
this->window.saveSettings();
this->core.saveSettings();
this->window.readGameVersion();
this->core.readGameVersion();
});
gameBoxLayout->addWidget(selectDirectoryButton);
@ -100,7 +101,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(encryptArgumentsBox, &QCheckBox::stateChanged, [=](int) {
getCurrentProfile().encryptArguments = encryptArgumentsBox->isChecked();
this->window.saveSettings();
this->core.saveSettings();
});
loginBoxLayout->addRow("Encrypt Game Arguments", encryptArgumentsBox);
@ -108,7 +109,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(enableDalamudBox, &QCheckBox::stateChanged, [=](int) {
getCurrentProfile().enableDalamud = enableDalamudBox->isChecked();
this->window.saveSettings();
this->core.saveSettings();
});
loginBoxLayout->addRow("Enable Dalamud Injection", enableDalamudBox);
@ -120,8 +121,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
getCurrentProfile().isSapphire = index == 1;
reloadControls();
this->window.reloadControls();
this->window.saveSettings();
this->core.saveSettings();
});
loginBoxLayout->addRow("Server Lobby", serverType);
@ -129,7 +129,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
lobbyServerURL = new QLineEdit();
connect(lobbyServerURL, &QLineEdit::editingFinished, [=] {
getCurrentProfile().lobbyURL = lobbyServerURL->text();
this->window.saveSettings();
this->core.saveSettings();
});
loginBoxLayout->addRow("Lobby URL", lobbyServerURL);
@ -137,8 +137,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(rememberUsernameBox, &QCheckBox::stateChanged, [=](int) {
getCurrentProfile().rememberUsername = rememberUsernameBox->isChecked();
this->window.reloadControls();
this->window.saveSettings();
this->core.saveSettings();
});
loginBoxLayout->addRow("Remember Username?", rememberUsernameBox);
@ -146,8 +145,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(rememberPasswordBox, &QCheckBox::stateChanged, [=](int) {
getCurrentProfile().rememberPassword = rememberPasswordBox->isChecked();
this->window.reloadControls();
this->window.saveSettings();
this->core.saveSettings();
});
loginBoxLayout->addRow("Remember Password?", rememberPasswordBox);
@ -163,7 +161,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
infoLabel->setWordWrap(true);
wineBoxLayout->addWidget(infoLabel);
winePathLabel = new QLabel(window.currentProfile().winePath);
winePathLabel = new QLabel();
winePathLabel->setWordWrap(true);
wineBoxLayout->addRow("Wine Executable", winePathLabel);
@ -184,19 +182,19 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(wineVersionCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index) {
getCurrentProfile().wineVersion = index;
this->window.readWineInfo(getCurrentProfile());
this->window.saveSettings();
this->core.readWineInfo(getCurrentProfile());
this->core.saveSettings();
this->reloadControls();
});
connect(selectWineButton, &QPushButton::pressed, [this] {
getCurrentProfile().winePath = QFileDialog::getOpenFileName(this, "Open Wine Executable");
this->window.saveSettings();
this->core.saveSettings();
this->reloadControls();
});
winePrefixDirectory = new QLabel(window.currentProfile().winePrefixPath);
winePrefixDirectory = new QLabel();
winePrefixDirectory->setWordWrap(true);
wineBoxLayout->addRow("Wine Prefix", winePrefixDirectory);
@ -204,7 +202,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(selectPrefixButton, &QPushButton::pressed, [this] {
getCurrentProfile().winePrefixPath = QFileDialog::getExistingDirectory(this, "Open Wine Prefix");
this->window.saveSettings();
this->core.saveSettings();
this->reloadControls();
});
wineBoxLayout->addWidget(selectPrefixButton);
@ -216,12 +214,11 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
wineBoxLayout->addWidget(openPrefixButton);
auto enableDXVKhud = new QCheckBox("Enable DXVK HUD");
enableDXVKhud->setChecked(window.currentProfile().enableDXVKhud);
wineBoxLayout->addWidget(enableDXVKhud);
connect(enableDXVKhud, &QCheckBox::stateChanged, [this](int state) {
this->window.currentProfile().enableDXVKhud = state;
this->window.settings.setValue("enableDXVKhud", static_cast<bool>(state));
getCurrentProfile().enableDXVKhud = state;
this->core.settings.setValue("enableDXVKhud", static_cast<bool>(state));
});
#endif
@ -237,7 +234,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(useEsync, &QCheckBox::stateChanged, [this](int state) {
getCurrentProfile().useEsync = state;
this->window.saveSettings();
this->core.saveSettings();
});
useGamescope = new QCheckBox("Use Gamescope");
@ -251,7 +248,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(useGamescope, &QCheckBox::stateChanged, [this](int state) {
getCurrentProfile().useGamescope = state;
this->window.saveSettings();
this->core.saveSettings();
});
useGamemode = new QCheckBox("Use Gamemode");
@ -265,7 +262,7 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window
connect(useGamemode, &QCheckBox::stateChanged, [this](int state) {
getCurrentProfile().useGamemode = state;
this->window.saveSettings();
this->core.saveSettings();
});
#endif
@ -282,15 +279,15 @@ void SettingsWindow::reloadControls() {
profileWidget->clear();
for(auto profile : window.profileList()) {
for(const auto& profile : core.profileList()) {
profileWidget->addItem(profile);
}
profileWidget->setCurrentRow(oldRow);
// deleting the main profile is unsupported behavior
deleteProfileButton->setEnabled(window.profileList().size() > 1);
deleteProfileButton->setEnabled(core.profileList().size() > 1);
ProfileSettings& profile = window.getProfile(profileWidget->currentRow());
ProfileSettings& profile = core.getProfile(profileWidget->currentRow());
nameEdit->setText(profile.name);
// game
@ -325,7 +322,7 @@ void SettingsWindow::reloadControls() {
}
ProfileSettings& SettingsWindow::getCurrentProfile() {
return this->window.getProfile(profileWidget->currentRow());
return this->core.getProfile(profileWidget->currentRow());
}
void SettingsWindow::openPath(const QString path) {

View file

@ -8,12 +8,13 @@
#include <QLabel>
#include <QPushButton>
class LauncherCore;
class LauncherWindow;
struct ProfileSettings;
class SettingsWindow : public QWidget {
public:
SettingsWindow(LauncherWindow& window, QWidget* parent = nullptr);
SettingsWindow(LauncherWindow& window, LauncherCore& core, QWidget* parent = nullptr);
public slots:
void reloadControls();
@ -48,4 +49,5 @@ private:
bool currentlyReloadingControls = false;
LauncherWindow& window;
LauncherCore& core;
};

View file

@ -6,7 +6,7 @@
#include "squarelauncher.h"
SquareBoot::SquareBoot(LauncherWindow& window, SquareLauncher& launcher) : window(window), launcher(launcher) {
SquareBoot::SquareBoot(LauncherCore& window, SquareLauncher& launcher) : window(window), launcher(launcher) {
}
@ -17,7 +17,7 @@ void SquareBoot::bootCheck(LoginInformation& info) {
QUrl url;
url.setScheme("http");
url.setHost("patch-bootver.ffxiv.com");
url.setPath(QString("/http/win32/ffxivneo_release_boot/%1").arg(window.currentProfile().bootVersion));
url.setPath(QString("/http/win32/ffxivneo_release_boot/%1").arg(info.settings->bootVersion));
url.setQuery(query);
auto request = QNetworkRequest(url);

View file

@ -1,16 +1,16 @@
#pragma once
#include "xivlauncher.h"
#include "launchercore.h"
class SquareLauncher;
class SquareBoot : public QObject {
public:
SquareBoot(LauncherWindow& window, SquareLauncher& launcher);
SquareBoot(LauncherCore& window, SquareLauncher& launcher);
void bootCheck(LoginInformation& info);
private:
LauncherWindow& window;
LauncherCore& window;
SquareLauncher& launcher;
};

View file

@ -6,9 +6,9 @@
#include <QRegularExpressionMatch>
#include <QMessageBox>
#include "xivlauncher.h"
#include "launchercore.h"
SquareLauncher::SquareLauncher(LauncherWindow& window) : window(window) {
SquareLauncher::SquareLauncher(LauncherCore& window) : window(window) {
}
@ -88,7 +88,7 @@ void SquareLauncher::login(const LoginInformation& info, const QUrl referer) {
auth.region = parts[5].toInt();
auth.maxExpansion = parts[13].toInt();
readExpansionVersions(auth.maxExpansion);
readExpansionVersions(info, auth.maxExpansion);
registerSession(info);
}
@ -103,7 +103,7 @@ void SquareLauncher::registerSession(const LoginInformation& info) {
QUrl url;
url.setScheme("https");
url.setHost("patch-gamever.ffxiv.com");
url.setPath(QString("/http/win32/ffxivneo_release_game/%1/%2").arg(window.currentProfile().gameVersion, SID));
url.setPath(QString("/http/win32/ffxivneo_release_game/%1/%2").arg(info.settings->gameVersion, SID));
auto request = QNetworkRequest(url);
window.setSSL(request);
@ -111,7 +111,7 @@ void SquareLauncher::registerSession(const LoginInformation& info) {
request.setRawHeader("User-Agent", "FFXIV PATCH CLIENT");
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
QString report = window.currentProfile().bootVersion + "=" + getBootHash();
QString report = info.settings->bootVersion + "=" + getBootHash(info);
for(int i = 0; i < expansionVersions.size(); i++)
report += QString("\nex%1\t%2").arg(QString::number(i + 1), expansionVersions[i]);
@ -121,7 +121,7 @@ void SquareLauncher::registerSession(const LoginInformation& info) {
if(reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
auth.SID = reply->rawHeader("X-Patch-Unique-Id");
window.launchGame(auth);
window.launchGame(*info.settings, auth);
} else {
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Failed the anti-tamper check. Please restore your game to the original state or update the game.");
messageBox->show();
@ -129,7 +129,7 @@ void SquareLauncher::registerSession(const LoginInformation& info) {
});
}
QString SquareLauncher::getBootHash() {
QString SquareLauncher::getBootHash(const LoginInformation& info) {
const QList<QString> fileList =
{
"ffxivboot.exe",
@ -142,7 +142,7 @@ QString SquareLauncher::getBootHash() {
QString result;
for (int i = 0; i < fileList.count(); i++) {
result += fileList[i] + "/" + getFileHash(window.currentProfile().gamePath + "/boot/" + fileList[i]);
result += fileList[i] + "/" + getFileHash(info.settings->gamePath + "/boot/" + fileList[i]);
if (i != fileList.length() - 1)
result += ",";
@ -151,9 +151,9 @@ QString SquareLauncher::getBootHash() {
return result;
}
void SquareLauncher::readExpansionVersions(int max) {
void SquareLauncher::readExpansionVersions(const LoginInformation& info, int max) {
expansionVersions.clear();
for(int i = 0; i < max; i++)
expansionVersions.push_back(window.readVersion(QString("%1/game/sqpack/ex%2/ex%2.ver").arg(window.currentProfile().gamePath, QString::number(i + 1))));
expansionVersions.push_back(window.readVersion(QString("%1/game/sqpack/ex%2/ex%2.ver").arg(info.settings->gamePath, QString::number(i + 1))));
}

View file

@ -1,25 +1,25 @@
#pragma once
#include "xivlauncher.h"
#include "launchercore.h"
class SquareLauncher : public QObject {
public:
SquareLauncher(LauncherWindow& window);
SquareLauncher(LauncherCore& window);
void getStored(const LoginInformation& info);
void login(const LoginInformation& info, const QUrl referer);
void login(const LoginInformation& info, QUrl referer);
void registerSession(const LoginInformation& info);
private:
QString getBootHash();
void readExpansionVersions(int max);
QString getBootHash(const LoginInformation& info);
void readExpansionVersions(const LoginInformation& info, int max);
QString stored, SID;
LoginAuth auth;
LauncherWindow& window;
LauncherCore& window;
QList<QString> expansionVersions;
};