mirror of
https://github.com/redstrate/Astra.git
synced 2025-04-20 03:37:47 +00:00
Add initial files
This commit is contained in:
parent
dfefe7a48c
commit
3ce5e74884
13 changed files with 680 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*build*/
|
||||
*.kdev4
|
||||
.idea/
|
||||
.DS_Store
|
25
CMakeLists.txt
Executable file
25
CMakeLists.txt
Executable file
|
@ -0,0 +1,25 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(xivlauncher)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
find_package(Qt6 COMPONENTS Core Widgets Network CONFIG REQUIRED)
|
||||
|
||||
add_subdirectory(external)
|
||||
|
||||
add_executable(xivlauncher
|
||||
src/main.cpp
|
||||
src/xivlauncher.cpp
|
||||
src/sapphirelauncher.cpp
|
||||
src/squareboot.cpp
|
||||
src/squarelauncher.cpp)
|
||||
|
||||
target_link_libraries(xivlauncher Qt6::Core Qt6::Widgets Qt6::Network qt6keychain)
|
||||
|
||||
# disgusting, thanks qtkeychain
|
||||
target_include_directories(xivlauncher PRIVATE
|
||||
${CMAKE_BINARY_DIR}/_deps/qtkeychain-src
|
||||
${CMAKE_BINARY_DIR}/_deps/qtkeychain-build)
|
10
README
Executable file
10
README
Executable file
|
@ -0,0 +1,10 @@
|
|||
# xivlauncher
|
||||
|
||||
Finally, a cross-platform FFXIV launcher. It should run on Windows, macOS (really) and Linux!
|
||||
|
||||
This is mostly a hobby project, but should be extremely useful to macOS players who otherwise have to deal with the aging old launcher. Linux users should also appreciate not having to deal with installing XIVQuickLauncher through wine :-)
|
||||
|
||||
## Features
|
||||
* Runs on native (Windows) and Wine (macOS, Linux) versions of FFXIV.
|
||||
* Can connect to the official Square Enix servers _as well_ as Sapphire servers.
|
||||
* Saving username and/or password.
|
12
external/CMakeLists.txt
vendored
Normal file
12
external/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
qtkeychain
|
||||
GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git
|
||||
GIT_TAG v0.12.0
|
||||
)
|
||||
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
set(QTKEYCHAIN_STATIC ON)
|
||||
|
||||
FetchContent_MakeAvailable(qtkeychain)
|
10
src/main.cpp
Executable file
10
src/main.cpp
Executable file
|
@ -0,0 +1,10 @@
|
|||
#include "xivlauncher.h"
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
LauncherWindow w;
|
||||
w.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
69
src/sapphirelauncher.cpp
Normal file
69
src/sapphirelauncher.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "sapphirelauncher.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
|
||||
SapphireLauncher::SapphireLauncher(LauncherWindow& window) : window(window) {
|
||||
|
||||
}
|
||||
|
||||
void SapphireLauncher::login(QString lobbyUrl, const LoginInformation& info) {
|
||||
QJsonObject data {
|
||||
{"username", info.username},
|
||||
{"pass", info.password}
|
||||
};
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(lobbyUrl);
|
||||
url.setPath("/sapphire-api/lobby/login");
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
|
||||
|
||||
auto reply = window.mgr->post(request, QJsonDocument(data).toJson(QJsonDocument::JsonFormat::Compact));
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
if(!document.isEmpty()) {
|
||||
LoginAuth auth;
|
||||
auth.SID = document["sId"].toString();
|
||||
auth.lobbyhost = document["lobbyHost"].toString();
|
||||
auth.frontierHost = document["frontierHost"].toString();
|
||||
auth.region = 3;
|
||||
|
||||
window.launch(auth);
|
||||
} else {
|
||||
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Invalid username/password.");
|
||||
messageBox->show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SapphireLauncher::registerAccount(QString lobbyUrl, const LoginInformation& info) {
|
||||
QJsonObject data {
|
||||
{"username", info.username},
|
||||
{"pass", info.password}
|
||||
};
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(lobbyUrl);
|
||||
url.setPath("/sapphire-api/lobby/createAccount");
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
|
||||
|
||||
auto reply = window.mgr->post(request, QJsonDocument(data).toJson(QJsonDocument::JsonFormat::Compact));
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
LoginAuth auth;
|
||||
auth.SID = document["sId"].toString();
|
||||
auth.lobbyhost = document["lobbyHost"].toString();
|
||||
auth.frontierHost = document["frontierHost"].toString();
|
||||
auth.region = 3;
|
||||
|
||||
window.launch(auth);
|
||||
});
|
||||
}
|
16
src/sapphirelauncher.h
Normal file
16
src/sapphirelauncher.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "xivlauncher.h"
|
||||
|
||||
class SapphireLauncher : QObject {
|
||||
public:
|
||||
SapphireLauncher(LauncherWindow& window);
|
||||
|
||||
void login(QString lobbyUrl, const LoginInformation& info);
|
||||
void registerAccount(QString lobbyUrl, const LoginInformation& info);
|
||||
|
||||
private:
|
||||
LauncherWindow& window;
|
||||
};
|
37
src/squareboot.cpp
Normal file
37
src/squareboot.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "squareboot.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "squarelauncher.h"
|
||||
|
||||
SquareBoot::SquareBoot(LauncherWindow& window, SquareLauncher& launcher) : window(window), launcher(launcher) {
|
||||
|
||||
}
|
||||
|
||||
void SquareBoot::bootCheck(LoginInformation& info) {
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("time", QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd-HH-mm"));
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost("patch-bootver.ffxiv.com");
|
||||
url.setPath(QString("/http/win32/ffxivneo_release_boot/%1").arg(window.bootVersion));
|
||||
url.setQuery(query);
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
request.setRawHeader("User-Agent", "FFXIV PATCH CLIENT");
|
||||
request.setRawHeader("Host", "patch-bootver.ffxiv.com");
|
||||
|
||||
auto reply = window.mgr->get(request);
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
QString response = reply->readAll();
|
||||
if(response.isEmpty()) {
|
||||
launcher.getStored(info);
|
||||
} else {
|
||||
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Failed to launch. The game may require an update, please use another launcher.");
|
||||
messageBox->show();
|
||||
}
|
||||
});
|
||||
}
|
16
src/squareboot.h
Normal file
16
src/squareboot.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "xivlauncher.h"
|
||||
|
||||
class SquareLauncher;
|
||||
|
||||
class SquareBoot : public QObject {
|
||||
public:
|
||||
SquareBoot(LauncherWindow& window, SquareLauncher& launcher);
|
||||
|
||||
void bootCheck(LoginInformation& info);
|
||||
|
||||
private:
|
||||
LauncherWindow& window;
|
||||
SquareLauncher& launcher;
|
||||
};
|
157
src/squarelauncher.cpp
Normal file
157
src/squarelauncher.cpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
#include "squarelauncher.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "xivlauncher.h"
|
||||
|
||||
SquareLauncher::SquareLauncher(LauncherWindow& window) : window(window) {
|
||||
|
||||
}
|
||||
|
||||
QString getFileHash(QString file) {
|
||||
auto f = QFile(file);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return "";
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(&f);
|
||||
|
||||
return QString("%1/%2").arg(QString::number(f.size()), hash.result().toHex());
|
||||
}
|
||||
|
||||
void SquareLauncher::getStored(const LoginInformation& info) {
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("lng", "en");
|
||||
query.addQueryItem("rgn", "3");
|
||||
query.addQueryItem("isft", "0");
|
||||
query.addQueryItem("cssmode", "1");
|
||||
query.addQueryItem("isnew", "1");
|
||||
query.addQueryItem("issteam", "0");
|
||||
|
||||
QUrl url("https://ffxiv-login.square-enix.com/oauth/ffxivarr/login/top");
|
||||
url.setQuery(query);
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
window.buildRequest(request);
|
||||
|
||||
QNetworkReply* reply = window.mgr->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
auto str = QString(reply->readAll());
|
||||
|
||||
QRegularExpression re(R"lit(\t<\s*input .* name="_STORED_" value="(?<stored>.*)">)lit");
|
||||
QRegularExpressionMatch match = re.match(str);
|
||||
if (match.hasMatch()) {
|
||||
stored = match.captured(1);
|
||||
login(info, url);
|
||||
} else {
|
||||
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Failed to contact SE servers. They may be in maintenance.");
|
||||
messageBox->show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SquareLauncher::login(const LoginInformation& info, const QUrl referer) {
|
||||
QUrlQuery postData;
|
||||
postData.addQueryItem("_STORED_", stored);
|
||||
postData.addQueryItem("sqexid", info.username);
|
||||
postData.addQueryItem("password", info.password);
|
||||
postData.addQueryItem("otppw", info.oneTimePassword);
|
||||
|
||||
QNetworkRequest request(QUrl("https://ffxiv-login.square-enix.com/oauth/ffxivarr/login/login.send"));
|
||||
window.buildRequest(request);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
|
||||
request.setRawHeader("Referer", referer.toEncoded());
|
||||
request.setRawHeader("Cache-Control", "no-cache");
|
||||
|
||||
auto reply = window.mgr->post(request, postData.toString(QUrl::FullyEncoded).toUtf8());
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
auto str = QString(reply->readAll());
|
||||
|
||||
QRegularExpression re(R"lit(window.external.user\("login=auth,ok,(?<launchParams>.*)\);)lit");
|
||||
QRegularExpressionMatch match = re.match(str);
|
||||
if(match.hasMatch()) {
|
||||
const auto parts = match.captured(1).split(',');
|
||||
|
||||
const bool terms = parts[3] == "1";
|
||||
const bool playable = parts[9] == "1";
|
||||
|
||||
if(!terms || !playable) {
|
||||
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Your game is unplayable. You may need to accept the terms from the official launcher.");
|
||||
messageBox->show();
|
||||
} else {
|
||||
SID = parts[1];
|
||||
auth.region = parts[5].toInt();
|
||||
auth.maxExpansion = parts[13].toInt();
|
||||
|
||||
readExpansionVersions(auth.maxExpansion);
|
||||
|
||||
registerSession(info);
|
||||
}
|
||||
} else {
|
||||
auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Invalid username/password.");
|
||||
messageBox->show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.gameVersion, SID));
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
window.setSSL(request);
|
||||
request.setRawHeader("X-Hash-Check", "enabled");
|
||||
request.setRawHeader("User-Agent", "FFXIV PATCH CLIENT");
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
|
||||
|
||||
QString report = window.bootVersion + "=" + getBootHash();
|
||||
|
||||
for(int i = 0; i < expansionVersions.size(); i++)
|
||||
report += QString("\nex%1\t%2").arg(QString::number(i + 1), expansionVersions[i]);
|
||||
|
||||
auto reply = window.mgr->post(request, report.toUtf8());
|
||||
connect(reply, &QNetworkReply::finished, [=] {
|
||||
if(reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
|
||||
auth.SID = reply->rawHeader("X-Patch-Unique-Id");
|
||||
|
||||
window.launch(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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QString SquareLauncher::getBootHash() {
|
||||
const QList<QString> fileList =
|
||||
{
|
||||
"ffxivboot.exe",
|
||||
"ffxivboot64.exe",
|
||||
"ffxivlauncher.exe",
|
||||
"ffxivlauncher64.exe",
|
||||
"ffxivupdater.exe",
|
||||
"ffxivupdater64.exe"
|
||||
};
|
||||
|
||||
QString result;
|
||||
for (int i = 0; i < fileList.count(); i++) {
|
||||
result += fileList[i] + "/" + getFileHash(window.gamePath + "/boot/" + fileList[i]);
|
||||
|
||||
if (i != fileList.length() - 1)
|
||||
result += ",";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SquareLauncher::readExpansionVersions(int max) {
|
||||
for(int i = 0; i < max; i++)
|
||||
expansionVersions.push_back(window.readVersion(QString("%1/game/sqpack/ex%2/ex%2.ver").arg(window.gamePath, QString::number(i + 1))));
|
||||
}
|
25
src/squarelauncher.h
Normal file
25
src/squarelauncher.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "xivlauncher.h"
|
||||
|
||||
class SquareLauncher : public QObject {
|
||||
public:
|
||||
SquareLauncher(LauncherWindow& window);
|
||||
|
||||
void getStored(const LoginInformation& info);
|
||||
|
||||
void login(const LoginInformation& info, const QUrl referer);
|
||||
|
||||
void registerSession(const LoginInformation& info);
|
||||
|
||||
private:
|
||||
QString getBootHash();
|
||||
void readExpansionVersions(int max);
|
||||
|
||||
QString stored, SID;
|
||||
LoginAuth auth;
|
||||
|
||||
LauncherWindow& window;
|
||||
|
||||
QList<QString> expansionVersions;
|
||||
};
|
248
src/xivlauncher.cpp
Executable file
248
src/xivlauncher.cpp
Executable file
|
@ -0,0 +1,248 @@
|
|||
#include <QPushButton>
|
||||
#include <QProcess>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
#include <QDir>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QRegularExpression>
|
||||
#include <QComboBox>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QCheckBox>
|
||||
#include <keychain.h>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "xivlauncher.h"
|
||||
#include "sapphirelauncher.h"
|
||||
#include "squarelauncher.h"
|
||||
#include "squareboot.h"
|
||||
|
||||
void LauncherWindow::setSSL(QNetworkRequest& request) {
|
||||
QSslConfiguration config;
|
||||
config.setProtocol(QSsl::AnyProtocol);
|
||||
config.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
|
||||
request.setSslConfiguration(config);
|
||||
}
|
||||
|
||||
void LauncherWindow::buildRequest(QNetworkRequest& request) {
|
||||
setSSL(request);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader,
|
||||
QString("SQEXAuthor/2.0.0(Windows 6.2; ja-jp; %1)").arg(QSysInfo::bootUniqueId()));
|
||||
request.setRawHeader("Accept",
|
||||
"image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*");
|
||||
request.setRawHeader("Accept-Encoding", "gzip, deflate");
|
||||
request.setRawHeader("Accept-Language", "en-us");
|
||||
}
|
||||
|
||||
void LauncherWindow::launch(const LoginAuth auth) {
|
||||
auto process = new QProcess(this);
|
||||
process->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
|
||||
bool isWine = false;
|
||||
QString winePath;
|
||||
QString ffxivPath;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
ffxivPath = gamePath + "\\game\\ffxiv_dx11.exe";
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
isWine = true;
|
||||
// TODO: this is assuming FFXIV is installed in /Applications
|
||||
winePath = "/Applications/FINAL FANTASY XIV ONLINE.app/Contents/SharedSupport/finalfantasyxiv/FINAL FANTASY XIV ONLINE/wine";
|
||||
ffxivPath = QDir::homePath() + "/Library/Application Support/FINAL FANTASY XIV ONLINE/Bottles/published_Final_Fantasy/drive_c/Program Files (x86)/SquareEnix/FINAL FANTASY XIV - A Realm Reborn/game/ffxiv_dx11.exe";
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
isWine = true;
|
||||
// TODO: this is assuming you want to use the wine in your PATH, which isn't always the case
|
||||
winePath = "wine";
|
||||
|
||||
// TODO: this is assuming it's in your default WINEPREFIX
|
||||
ffxivPath = gamePath + "/game/ffxiv_dx11.exe";
|
||||
process->setWorkingDirectory(gamePath + "/game/");
|
||||
#endif
|
||||
|
||||
QList<QString> arguments;
|
||||
if (isWine) {
|
||||
arguments.push_back(ffxivPath);
|
||||
}
|
||||
|
||||
// i wonder what these mean...
|
||||
arguments.push_back("DEV.DataPathType=1");
|
||||
arguments.push_back("DEV.UseSqPack=1");
|
||||
// by the way, it looks like setting graphics options is possible via these too, i wonder what
|
||||
// else is hiding :-)))
|
||||
|
||||
arguments.push_back(QString("DEV.MaxEntitledExpansionID=%1").arg(auth.maxExpansion));
|
||||
arguments.push_back(QString("DEV.TestSID=%1").arg(auth.SID));
|
||||
arguments.push_back(QString("SYS.Region=%1").arg(auth.region));
|
||||
arguments.push_back(QString("language=%1").arg(language));
|
||||
arguments.push_back(QString("ver=%1").arg(gameVersion));
|
||||
|
||||
if(!auth.lobbyhost.isEmpty()) {
|
||||
arguments.push_back(QString("DEV.GMServerHost=%1").arg(auth.frontierHost));
|
||||
for(int i = 1; i < 9; i++)
|
||||
arguments.push_back(QString("DEV.LobbyHost0%1=%2 DEV.LobbyPort0%1=54994").arg(QString::number(i), auth.lobbyhost));
|
||||
}
|
||||
|
||||
if (isWine) {
|
||||
process->start(winePath, arguments);
|
||||
} else {
|
||||
process->start(ffxivPath, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
QString LauncherWindow::readVersion(QString path) {
|
||||
QFile file(path);
|
||||
file.open(QFile::OpenModeFlag::ReadOnly);
|
||||
|
||||
return file.readAll();
|
||||
}
|
||||
|
||||
void LauncherWindow::readInitialInformation() {
|
||||
#if defined(Q_OS_WIN)
|
||||
gamePath = "C:\\Program Files (x86\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn";
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
gamePath = QDir::homePath() + "/Library/Application Support/FINAL FANTASY XIV ONLINE/Bottles/published_Final_Fantasy/drive_c/Program Files (x86)/SquareEnix/FINAL FANTASY XIV - A Realm Reborn";
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
// TODO: this is assuming it's in your default WINEPREFIX
|
||||
gamePath = QDir::homePath() + "/.wine/drive_c/Program Files (x86)/SquareEnix/FINAL FANTASY XIV - A Realm Reborn";
|
||||
#endif
|
||||
|
||||
bootVersion = readVersion(gamePath + "/boot/ffxivboot.ver");
|
||||
gameVersion = readVersion(gamePath + "/game/ffxivgame.ver");
|
||||
}
|
||||
|
||||
LauncherWindow::LauncherWindow(QWidget* parent) :
|
||||
QMainWindow(parent) {
|
||||
mgr = new QNetworkAccessManager();
|
||||
sapphireLauncher = new SapphireLauncher(*this);
|
||||
squareLauncher = new SquareLauncher(*this);
|
||||
squareBoot = new SquareBoot(*this, *squareLauncher);
|
||||
|
||||
const auto savedServerType = settings.value("serverType", 0).toInt();
|
||||
const auto savedLobbyURL = settings.value("lobbyURL", "127.0.0.1").toString();
|
||||
const auto shouldRememberUsername = settings.value("rememberUsername", false).toBool();
|
||||
const auto shouldRememberPassword = settings.value("rememberPassword", false).toBool();
|
||||
|
||||
auto layout = new QFormLayout();
|
||||
|
||||
auto serverType = new QComboBox();
|
||||
serverType->insertItem(0, "Square Enix");
|
||||
serverType->insertItem(1, "Sapphire");
|
||||
serverType->setCurrentIndex(savedServerType);
|
||||
|
||||
layout->addRow("Server Lobby", serverType);
|
||||
|
||||
auto lobbyServerURL = new QLineEdit();
|
||||
lobbyServerURL->setText(savedLobbyURL);
|
||||
layout->addRow("Lobby URL", lobbyServerURL);
|
||||
|
||||
auto usernameEdit = new QLineEdit();
|
||||
layout->addRow("Username", usernameEdit);
|
||||
|
||||
if(shouldRememberUsername) {
|
||||
auto job = new QKeychain::ReadPasswordJob("LauncherWindow");
|
||||
job->setKey("username");
|
||||
job->start();
|
||||
|
||||
connect(job, &QKeychain::ReadPasswordJob::finished, [=](QKeychain::Job* j) {
|
||||
usernameEdit->setText(job->textData());
|
||||
});
|
||||
}
|
||||
|
||||
auto rememberUsernameBox = new QCheckBox();
|
||||
rememberUsernameBox->setChecked(shouldRememberUsername);
|
||||
layout->addRow("Remember Username?", rememberUsernameBox);
|
||||
|
||||
auto passwordEdit = new QLineEdit();
|
||||
passwordEdit->setEchoMode(QLineEdit::EchoMode::Password);
|
||||
layout->addRow("Password", passwordEdit);
|
||||
|
||||
if(shouldRememberPassword) {
|
||||
auto job = new QKeychain::ReadPasswordJob("LauncherWindow");
|
||||
job->setKey("password");
|
||||
job->start();
|
||||
|
||||
connect(job, &QKeychain::ReadPasswordJob::finished, [=](QKeychain::Job* j) {
|
||||
passwordEdit->setText(job->textData());
|
||||
});
|
||||
}
|
||||
|
||||
auto rememberPasswordBox = new QCheckBox();
|
||||
rememberPasswordBox->setChecked(shouldRememberPassword);
|
||||
layout->addRow("Remember Password?", rememberPasswordBox);
|
||||
|
||||
auto otpEdit = new QLineEdit();
|
||||
layout->addRow("One-Time Password", otpEdit);
|
||||
|
||||
auto loginButton = new QPushButton("Login");
|
||||
layout->addRow(loginButton);
|
||||
|
||||
auto registerButton = new QPushButton("Register");
|
||||
layout->addRow(registerButton);
|
||||
|
||||
const auto refreshControls = [=](int index) {
|
||||
lobbyServerURL->setEnabled(index == 1);
|
||||
registerButton->setEnabled(index == 1);
|
||||
otpEdit->setEnabled(index == 0);
|
||||
};
|
||||
refreshControls(serverType->currentIndex());
|
||||
|
||||
connect(serverType, &QComboBox::currentIndexChanged, [=](int index) {
|
||||
refreshControls(index);
|
||||
});
|
||||
|
||||
auto emptyWidget = new QWidget();
|
||||
emptyWidget->setLayout(layout);
|
||||
setCentralWidget(emptyWidget);
|
||||
|
||||
readInitialInformation();
|
||||
|
||||
connect(loginButton, &QPushButton::released, [=] {
|
||||
auto info = LoginInformation{usernameEdit->text(), passwordEdit->text(), otpEdit->text()};
|
||||
|
||||
settings.setValue("rememberUsername", rememberUsernameBox->checkState() == Qt::CheckState::Checked);
|
||||
if(rememberUsernameBox->checkState() == Qt::CheckState::Checked) {
|
||||
auto job = new QKeychain::WritePasswordJob("LauncherWindow");
|
||||
job->setTextData(usernameEdit->text());
|
||||
job->setKey("username");
|
||||
job->start();
|
||||
}
|
||||
|
||||
settings.setValue("rememberPassword", rememberPasswordBox->checkState() == Qt::CheckState::Checked);
|
||||
if(rememberPasswordBox->checkState() == Qt::CheckState::Checked) {
|
||||
auto job = new QKeychain::WritePasswordJob("LauncherWindow");
|
||||
job->setTextData(passwordEdit->text());
|
||||
job->setKey("password");
|
||||
job->start();
|
||||
}
|
||||
|
||||
settings.setValue("serverType", serverType->currentIndex());
|
||||
settings.setValue("lobbyURL", lobbyServerURL->text());
|
||||
|
||||
if(serverType->currentIndex() == 0) {
|
||||
// begin se's booting process
|
||||
squareBoot->bootCheck(info);
|
||||
} else {
|
||||
sapphireLauncher->login(lobbyServerURL->text(), info);
|
||||
}
|
||||
});
|
||||
|
||||
connect(registerButton, &QPushButton::released, [=] {
|
||||
if(serverType->currentIndex() == 1) {
|
||||
auto info = LoginInformation{usernameEdit->text(), passwordEdit->text(), otpEdit->text()};
|
||||
sapphireLauncher->registerAccount(lobbyServerURL->text(), info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LauncherWindow::~LauncherWindow() = default;
|
51
src/xivlauncher.h
Executable file
51
src/xivlauncher.h
Executable file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QFuture>
|
||||
#include <QSettings>
|
||||
|
||||
class SapphireLauncher;
|
||||
class SquareLauncher;
|
||||
class SquareBoot;
|
||||
|
||||
struct LoginInformation {
|
||||
QString username, password, oneTimePassword;
|
||||
};
|
||||
|
||||
struct LoginAuth {
|
||||
QString SID;
|
||||
int region = 2; // america?
|
||||
int maxExpansion = 1;
|
||||
|
||||
// if empty, dont set on the client
|
||||
QString lobbyhost, frontierHost;
|
||||
};
|
||||
|
||||
class LauncherWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LauncherWindow(QWidget* parent = nullptr);
|
||||
|
||||
~LauncherWindow() override;
|
||||
|
||||
QNetworkAccessManager* mgr;
|
||||
|
||||
int language = 1; // 1 is english, thats all i know
|
||||
QString gamePath;
|
||||
QString bootVersion, gameVersion;
|
||||
|
||||
void launch(const LoginAuth auth);
|
||||
void buildRequest(QNetworkRequest& request);
|
||||
void setSSL(QNetworkRequest& request);
|
||||
QString readVersion(QString path);
|
||||
|
||||
private:
|
||||
void readInitialInformation();
|
||||
|
||||
SapphireLauncher* sapphireLauncher;
|
||||
SquareBoot* squareBoot;
|
||||
SquareLauncher* squareLauncher;
|
||||
|
||||
QSettings settings;
|
||||
};
|
Loading…
Add table
Reference in a new issue