From 3ce5e748846ea68c2ca11b25f4a76a9f30fa4d39 Mon Sep 17 00:00:00 2001 From: redstrate Date: Mon, 1 Nov 2021 09:54:58 -0400 Subject: [PATCH] Add initial files --- .gitignore | 4 + CMakeLists.txt | 25 ++++ README | 10 ++ external/CMakeLists.txt | 12 ++ src/main.cpp | 10 ++ src/sapphirelauncher.cpp | 69 +++++++++++ src/sapphirelauncher.h | 16 +++ src/squareboot.cpp | 37 ++++++ src/squareboot.h | 16 +++ src/squarelauncher.cpp | 157 +++++++++++++++++++++++++ src/squarelauncher.h | 25 ++++ src/xivlauncher.cpp | 248 +++++++++++++++++++++++++++++++++++++++ src/xivlauncher.h | 51 ++++++++ 13 files changed, 680 insertions(+) create mode 100644 .gitignore create mode 100755 CMakeLists.txt create mode 100755 README create mode 100644 external/CMakeLists.txt create mode 100755 src/main.cpp create mode 100644 src/sapphirelauncher.cpp create mode 100644 src/sapphirelauncher.h create mode 100644 src/squareboot.cpp create mode 100644 src/squareboot.h create mode 100644 src/squarelauncher.cpp create mode 100644 src/squarelauncher.h create mode 100755 src/xivlauncher.cpp create mode 100755 src/xivlauncher.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cac679 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*build*/ +*.kdev4 +.idea/ +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..84dd193 --- /dev/null +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/README b/README new file mode 100755 index 0000000..75d1c4d --- /dev/null +++ b/README @@ -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. diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt new file mode 100644 index 0000000..b021468 --- /dev/null +++ b/external/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100755 index 0000000..0ef0b9a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,10 @@ +#include "xivlauncher.h" +#include + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + LauncherWindow w; + w.show(); + + return app.exec(); +} diff --git a/src/sapphirelauncher.cpp b/src/sapphirelauncher.cpp new file mode 100644 index 0000000..523e7af --- /dev/null +++ b/src/sapphirelauncher.cpp @@ -0,0 +1,69 @@ +#include "sapphirelauncher.h" + +#include +#include +#include +#include + +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); + }); +} \ No newline at end of file diff --git a/src/sapphirelauncher.h b/src/sapphirelauncher.h new file mode 100644 index 0000000..5328be7 --- /dev/null +++ b/src/sapphirelauncher.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#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; +}; \ No newline at end of file diff --git a/src/squareboot.cpp b/src/squareboot.cpp new file mode 100644 index 0000000..ed07e98 --- /dev/null +++ b/src/squareboot.cpp @@ -0,0 +1,37 @@ +#include "squareboot.h" + +#include +#include +#include + +#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(); + } + }); +} \ No newline at end of file diff --git a/src/squareboot.h b/src/squareboot.h new file mode 100644 index 0000000..111d0d1 --- /dev/null +++ b/src/squareboot.h @@ -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; +}; \ No newline at end of file diff --git a/src/squarelauncher.cpp b/src/squarelauncher.cpp new file mode 100644 index 0000000..56a4967 --- /dev/null +++ b/src/squarelauncher.cpp @@ -0,0 +1,157 @@ +#include "squarelauncher.h" + +#include +#include +#include +#include +#include + +#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="(?.*)">)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,(?.*)\);)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 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)))); +} \ No newline at end of file diff --git a/src/squarelauncher.h b/src/squarelauncher.h new file mode 100644 index 0000000..1bc5531 --- /dev/null +++ b/src/squarelauncher.h @@ -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 expansionVersions; +}; \ No newline at end of file diff --git a/src/xivlauncher.cpp b/src/xivlauncher.cpp new file mode 100755 index 0000000..8bdefad --- /dev/null +++ b/src/xivlauncher.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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; diff --git a/src/xivlauncher.h b/src/xivlauncher.h new file mode 100755 index 0000000..2047f91 --- /dev/null +++ b/src/xivlauncher.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +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; +};