diff --git a/.clang-format b/.clang-format
deleted file mode 100644
index aa0779e..0000000
--- a/.clang-format
+++ /dev/null
@@ -1,33 +0,0 @@
----
-AllowShortIfStatementsOnASingleLine: Never
-CompactNamespaces: 'false'
-DisableFormat: 'false'
-IndentCaseLabels: 'true'
-IndentPPDirectives: BeforeHash
-IndentWidth: '4'
-Language: Cpp
-NamespaceIndentation: All
-PointerAlignment: Left
-ReflowComments: 'true'
-SortIncludes: 'true'
-SortUsingDeclarations: 'true'
-SpacesInCStyleCastParentheses: 'false'
-Standard: Cpp11
-TabWidth: '0'
-UseTab: Never
-AllowShortEnumsOnASingleLine: false
-BraceWrapping:
- AfterEnum: true
-AccessModifierOffset: -4
-SpaceAfterTemplateKeyword: 'false'
-AllowAllParametersOfDeclarationOnNextLine: false
-AlignAfterOpenBracket: AlwaysBreak
-BinPackArguments: false
-BinPackParameters: false
-ColumnLimit: 120
-AllowShortBlocksOnASingleLine: 'false'
-AllowShortCaseLabelsOnASingleLine: 'false'
-AllowShortFunctionsOnASingleLine: 'Empty'
-AllowShortLambdasOnASingleLine: 'Empty'
-AllowShortLoopsOnASingleLine: 'false'
-SeparateDefinitionBlocks : 'Always'
diff --git a/.gitignore b/.gitignore
index 4cbe1d5..a897565 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
.DS_Store
.directory
*.flatpak
-export/
\ No newline at end of file
+export/
+.clang-format
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed9d2c7..cdb64ef 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,30 +1,51 @@
cmake_minimum_required(VERSION 3.16)
-project(Astra)
+project(Astra VERSION 0.5.0 LANGUAGES CXX)
# build options used for distributors
option(BUILD_FLATPAK "Build for Flatpak." OFF)
# options for features you may want or need
-option(ENABLE_WATCHDOG "Build support for Watchdog, requires an X11 system." OFF)
-option(ENABLE_STEAM "Build with Steam support, requires supplying the Steam SDK." OFF)
+option(ENABLE_WATCHDOG "Build support for Watchdog, requires X11." OFF)
+option(ENABLE_STEAM "Build with Steam support, requires supplying the Steam SDK yourself." OFF)
option(ENABLE_GAMEMODE "Build with Feral GameMode support, requires the daemon to be installed." ON)
-option(ENABLE_TABLET "Build support for the tablet interface, meant for devices like the Steam Deck." ON)
-option(ENABLE_DESKTOP "Build support for the desktop interface, meant to be used on desktops and laptops." ON)
-option(ENABLE_CLI "Build support for the command-line interface, meant for scripting and automation." ON)
+set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
-find_package(Qt5 COMPONENTS Core Widgets Network Quick CONFIG REQUIRED)
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(QT_MIN_VERSION 5.15)
+set(KF5_MIN_VERSION 5.83)
+
+find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
+list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
+
+include(KDEInstallDirs)
+include(ECMFindQmlModule)
+include(KDECMakeSettings)
+include(KDECompilerSettings NO_POLICY_SCOPE)
+include(ECMSetupVersion)
+include(ECMGenerateHeaders)
+include(ECMPoQmTools)
+include(KDEGitCommitHooks)
+include(KDEClangFormat)
+
+find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE REQUIRED COMPONENTS
+ Core
+ Widgets
+ Network
+ QuickControls2)
+find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Config CoreAddons)
+find_package(PkgConfig REQUIRED)
if (ENABLE_WATCHDOG)
- find_package(PkgConfig REQUIRED)
pkg_search_module(TESSERACT REQUIRED tesseract)
pkg_search_module(LEPTONICA REQUIRED lept)
endif ()
if (ENABLE_GAMEMODE)
- find_package(PkgConfig REQUIRED)
pkg_search_module(GAMEMODE REQUIRED gamemode)
endif ()
@@ -34,13 +55,20 @@ if (ENABLE_STEAM)
INTERFACE_INCLUDE_DIRECTORIES ${STEAMWORKS_INCLUDE_DIR}
IMPORTED_LOCATION ${STEAMWORKS_LIBRARIES})
- if(BUILD_FLATPAK)
+ if (BUILD_FLATPAK)
install(IMPORTED_RUNTIME_ARTIFACTS Steamworks)
- endif()
+ endif ()
endif ()
find_package(Qt5Keychain REQUIRED)
find_package(QuaZip-Qt5 REQUIRED)
add_subdirectory(external)
-add_subdirectory(launcher)
\ No newline at end of file
+add_subdirectory(launcher)
+
+feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
+kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
+
+kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
\ No newline at end of file
diff --git a/README.md b/README.md
index 58b7478..09a93f2 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Astra
A custom FFXIV launcher that supports multiple accounts, [Dalamud](https://github.com/goatcorp/Dalamud) plugins and runs
-natively on Windows, macOS and Linux!
+natively on Linux!
### Notice
@@ -16,26 +16,22 @@ If you still have questions, please read the [FAQ](https://xiv.zone/astra/faq) f
## Features
-* Traditional desktop interface which looks native to your system, utilizing Qt - a proven application framework.
- * Supports single-window scenarios such as the Steam Deck seamlessly.
-* Native support for Windows, macOS and Linux!
-* Handles running Wine for macOS and Linux users - creating a seamless and native-feeling launcher experience, compared
- to running other FFXIV launchers in Wine.
+* Handles running Wine for you, creating a seamless and native-feeling launcher experience!
* Can also easily enable several Linux-specific enhancements such as Fsync or configuring Gamescope.
* Multiple account support!
- * Most settings can be set per-profile.
-* Easily install and use Dalamud plugins, just like XIVQuickLauncher.
-* Patches the game, just like the official launcher!
+ * Can associate a Lodestone character with an account to use as an avatar.
+* Easily install and use Dalamud plugins.
+* Game patching support.
* Securely login to the official Square Enix lobbies, as well as Sapphire servers.
* Game arguments are encrypted by default, providing the same level of security as other launchers.
* Saving account usernames and passwords are also supported, and is never stored plaintext.
-* Can easily install FFXIV on new systems right from the launcher, bypassing the normal InstallShield installer.
+* Can install FFXIV on new systems for you, bypassing the normal InstallShield installer.
## Installation
-Precompiled binaries are available for Windows and macOS users, which you can [download from the website](https://xiv.zone/astra/install).
+Precompiled binaries are available [to download from the website](https://xiv.zone/astra/install).
-For Linux users, there is numerous options available to you:
+There are also numerous options available:
* _Flatpak_ - Instructions can be found in the [Flatpak installation](https://xiv.zone/astra/install/#linux) section.
* _AUR_ - You can find the [AUR package here](https://aur.archlinux.org/packages/astra-launcher).
@@ -52,11 +48,7 @@ This functionality will change in the future to ease distribution packaging. You
the `USE_OWN_LIBRARIES` CMake option.
[The wiki](https://man.sr.ht/~redstrate/astra/) has dedicated platform-specific pages for build instructions as well as
-important information:
-
-* [Windows](https://man.sr.ht/~redstrate/astra/windows-usage.md)
-* [macOS](https://man.sr.ht/~redstrate/astra/macos-usage.md)
-* [Linux](https://man.sr.ht/~redstrate/astra/linux-usage.md)
+[important usage information](https://man.sr.ht/~redstrate/astra/linux-usage.md).
## Contributing and Support
diff --git a/compatibilitytool/toolmanifest.vdf b/compatibilitytool/toolmanifest.vdf
index 991e02a..9704382 100644
--- a/compatibilitytool/toolmanifest.vdf
+++ b/compatibilitytool/toolmanifest.vdf
@@ -1,4 +1,5 @@
"manifest"
{
+ "version" "2"
"commandline" "/astra --steam %verb%"
}
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index c29f0cb..06cb15d 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -1,3 +1,5 @@
+set(BUILD_SHARED_LIBS OFF)
+
add_subdirectory(libbaseencode)
add_subdirectory(libcotp)
@@ -6,9 +8,8 @@ include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
- GIT_TAG v0.3.5
+ GIT_TAG v0.4.2
)
-
FetchContent_MakeAvailable(Corrosion)
FetchContent_Declare(
@@ -16,7 +17,6 @@ FetchContent_Declare(
GIT_REPOSITORY https://git.sr.ht/~redstrate/libphysis
GIT_TAG main
)
-
FetchContent_MakeAvailable(libphysis)
corrosion_import_crate(MANIFEST_PATH ${libphysis_SOURCE_DIR}/Cargo.toml
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 2263d82..256e17d 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -1,29 +1,88 @@
-add_subdirectory(core)
-add_subdirectory(desktop)
+add_executable(astra)
+target_sources(astra PRIVATE
+ include/account.h
+ include/accountmanager.h
+ include/assetupdater.h
+ include/encryptedarg.h
+ include/gameinstaller.h
+ include/headline.h
+ include/launchercore.h
+ include/patcher.h
+ include/profile.h
+ include/profilemanager.h
+ include/sapphirelauncher.h
+ include/squareboot.h
+ include/squarelauncher.h
+ include/steamapi.h
-add_executable(astra
- main.cpp)
+ src/account.cpp
+ src/accountmanager.cpp
+ src/assetupdater.cpp
+ src/encryptedarg.cpp
+ src/gameinstaller.cpp
+ src/launchercore.cpp
+ src/main.cpp
+ src/patcher.cpp
+ src/profile.cpp
+ src/profilemanager.cpp
+ src/sapphirelauncher.cpp
+ src/squareboot.cpp
+ src/squarelauncher.cpp
+ src/steamapi.cpp
-target_link_libraries(astra PUBLIC
- astra_core
- astra_desktop)
-target_compile_features(astra PUBLIC cxx_std_17)
-set_target_properties(astra PROPERTIES CXX_EXTENSIONS OFF)
+ resources.qrc)
+kconfig_add_kcfg_files(astra GENERATE_MOC config.kcfgc accountconfig.kcfgc profileconfig.kcfgc)
+target_include_directories(astra PRIVATE include)
+target_link_libraries(astra PRIVATE
+ physis
+ cotp
+ crypto
+ QuaZip::QuaZip
+ Qt5Keychain::Qt5Keychain
+ Qt5::Core
+ Qt5::Network
+ Qt5::Widgets
+ Qt5::Quick
+ Qt5::QuickControls2
+ KF5::Kirigami2
+ KF5::I18n
+ KF5::ConfigCore
+ KF5::ConfigGui
+ KF5::CoreAddons)
-# meant for including the license text
-file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE LICENSE_TXT)
-STRING(REPLACE "\n" " \\n" LICENSE_TXT ${LICENSE_TXT})
-STRING(REPLACE "\"" "\"\"" LICENSE_TXT ${LICENSE_TXT})
+if (ENABLE_WATCHDOG)
+ target_sources(astra PRIVATE
+ include/gameparser.h
+ include/watchdog.h
-configure_file(${CMAKE_CURRENT_LIST_DIR}/../cmake/license.h.in
- ${CMAKE_BINARY_DIR}/license.h)
+ src/gameparser.cpp
+ src/watchdog.cpp)
+ target_link_libraries(astra PRIVATE
+ ${TESSERACT_LIBRARIES}
+ ${LEPTONICA_LIBRARIES}
+ X11
+ Xcomposite
+ Xrender)
+
+ target_include_directories(astra_core PRIVATE ${TESSERACT_INCLUDE_DIRS} ${LEPTONICA_INCLUDE_DIRS})
+ target_compile_definitions(astra_core PRIVATE ENABLE_WATCHDOG)
+endif ()
if (BUILD_FLATPAK)
target_compile_definitions(astra PRIVATE FLATPAK)
endif ()
-install(TARGETS astra
- DESTINATION "${INSTALL_BIN_PATH}")
+if (ENABLE_STEAM)
+ target_link_libraries(astra PRIVATE Steamworks)
+ target_compile_definitions(astra PRIVATE ENABLE_STEAM)
+endif ()
+
+if (ENABLE_GAMEMODE)
+ target_link_libraries(astra PRIVATE ${GAMEMODE_LIBRARIES})
+ target_compile_definitions(astra PRIVATE ENABLE_GAMEMODE)
+endif ()
+
+install(TARGETS astra ${KF${QT_MAJOR_VERSION}_INSTALL_TARGETS_DEFAULT_ARGS})
if (WIN32)
get_target_property(QMAKE_EXE Qt5::qmake IMPORTED_LOCATION)
diff --git a/launcher/accountconfig.kcfg b/launcher/accountconfig.kcfg
new file mode 100644
index 0000000..d17259e
--- /dev/null
+++ b/launcher/accountconfig.kcfg
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ WindowsStandalone
+
+
+
+
+
diff --git a/launcher/accountconfig.kcfgc b/launcher/accountconfig.kcfgc
new file mode 100644
index 0000000..3c432f9
--- /dev/null
+++ b/launcher/accountconfig.kcfgc
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2023 Joshua Goins
+# SPDX-License-Identifier: LGPL-2.1-or-later
+File=accountconfig.kcfg
+ClassName=AccountConfig
+Mutators=true
+DefaultValueGetters=true
+GenerateProperties=true
+ParentInConstructor=true
+Singleton=false
diff --git a/launcher/config.kcfg b/launcher/config.kcfg
new file mode 100644
index 0000000..1483770
--- /dev/null
+++ b/launcher/config.kcfg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+
+
+
diff --git a/launcher/config.kcfgc b/launcher/config.kcfgc
new file mode 100644
index 0000000..972b99a
--- /dev/null
+++ b/launcher/config.kcfgc
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2023 Joshua Goins
+# SPDX-License-Identifier: LGPL-2.1-or-later
+File=config.kcfg
+ClassName=Config
+Mutators=true
+DefaultValueGetters=true
+GenerateProperties=true
+ParentInConstructor=true
+Singleton=true
diff --git a/launcher/core/CMakeLists.txt b/launcher/core/CMakeLists.txt
deleted file mode 100644
index af4170e..0000000
--- a/launcher/core/CMakeLists.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-set(HEADERS
- include/config.h
- include/encryptedarg.h
- include/gameinstaller.h
- include/headline.h
- include/launchercore.h
- include/sapphirelauncher.h
- include/squareboot.h
- include/squarelauncher.h
- include/patcher.h
- include/steamapi.h
- include/assetupdater.h)
-
-set(SRC
- src/encryptedarg.cpp
- src/gameinstaller.cpp
- src/headline.cpp
- src/launchercore.cpp
- src/sapphirelauncher.cpp
- src/squareboot.cpp
- src/squarelauncher.cpp
- src/patcher.cpp
- src/steamapi.cpp
- src/assetupdater.cpp)
-
-if (ENABLE_WATCHDOG)
- set(HEADERS ${HEADERS}
- include/gameparser.h
- include/watchdog.h)
-
- set(SRC ${SRC}
- src/gameparser.cpp
- src/watchdog.cpp)
-
- set(LIBRARIES ${LIBRARIES}
- ${TESSERACT_LIBRARIES}
- ${LEPTONICA_LIBRARIES}
- X11
- Xcomposite
- Xrender)
-endif ()
-
-if(ENABLE_STEAM)
- set(LIBRARIES ${LIBRARIES}
- Steamworks)
-endif()
-
-if(ENABLE_GAMEMODE)
- set(LIBRARIES ${LIBRARIES}
- ${GAMEMODE_LIBRARIES})
-endif()
-
-add_library(astra_core STATIC ${HEADERS} ${SRC})
-target_include_directories(astra_core PUBLIC
- ${KEYCHAIN_INCLUDE_DIRS}
- ${QUAZIP_INCLUDE_DIRS}
- ${CMAKE_BINARY_DIR}
- include)
-target_link_libraries(astra_core PUBLIC
- physis
- z # FIXME: remove!
- QuaZip::QuaZip
- Qt5Keychain::Qt5Keychain
- ${LIBRARIES}
- Qt5::Core
- Qt5::Network
- Qt5::Widgets # widgets is required by watchdog, to be fixed/removed later
- Qt5::Quick # required for some type registrations
- PRIVATE
- cotp
- crypto) # desktop is currently required by the core, to be fixed/removed later
-
-if (ENABLE_WATCHDOG)
- target_include_directories(astra_core PUBLIC ${TESSERACT_INCLUDE_DIRS} ${LEPTONICA_INCLUDE_DIRS})
-
- target_compile_definitions(astra_core PUBLIC ENABLE_WATCHDOG)
-endif ()
-
-if(ENABLE_GAMEMODE)
- target_compile_definitions(astra_core PUBLIC ENABLE_GAMEMODE)
-endif()
-
-if(ENABLE_STEAM)
- target_compile_definitions(astra_core PUBLIC ENABLE_STEAM)
-endif()
\ No newline at end of file
diff --git a/launcher/core/include/config.h b/launcher/core/include/config.h
deleted file mode 100644
index 802988d..0000000
--- a/launcher/core/include/config.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-constexpr const char* version = "0.4.1";
\ No newline at end of file
diff --git a/launcher/core/include/encryptedarg.h b/launcher/core/include/encryptedarg.h
deleted file mode 100644
index f521cab..0000000
--- a/launcher/core/include/encryptedarg.h
+++ /dev/null
@@ -1,42 +0,0 @@
-#pragma once
-
-#include
-#include
-
-// from xivdev
-static char ChecksumTable[] = {'f', 'X', '1', 'p', 'G', 't', 'd', 'S', '5', 'C', 'A', 'P', '4', '_', 'V', 'L'};
-
-inline char GetChecksum(const unsigned int key) {
- auto value = key & 0x000F0000;
- return ChecksumTable[value >> 16];
-}
-
-uint32_t TickCount();
-
-inline QString encryptGameArg(const QString& arg) {
- const uint32_t rawTicks = TickCount();
- const uint32_t ticks = rawTicks & 0xFFFFFFFFu;
- const uint32_t key = ticks & 0xFFFF0000u;
-
- char buffer[9]{};
- sprintf(buffer, "%08x", key);
-
- Blowfish const* blowfish = physis_blowfish_initialize(reinterpret_cast(buffer), 9);
-
- uint8_t* out_data = nullptr;
- uint32_t out_size = 0;
-
- QByteArray toEncrypt = (QString(" /T =%1").arg(ticks) + arg).toUtf8();
-
- physis_blowfish_encrypt(
- blowfish, reinterpret_cast(toEncrypt.data()), toEncrypt.size(), &out_data, &out_size);
-
- const QByteArray encryptedArg =
- QByteArray::fromRawData(reinterpret_cast(out_data), static_cast(out_size));
-
- const QString base64 = encryptedArg.toBase64(
- QByteArray::Base64Option::Base64UrlEncoding | QByteArray::Base64Option::KeepTrailingEquals);
- const char checksum = GetChecksum(key);
-
- return QString("//**sqex0003%1%2**//").arg(base64, QString(checksum));
-}
\ No newline at end of file
diff --git a/launcher/core/include/gameinstaller.h b/launcher/core/include/gameinstaller.h
deleted file mode 100644
index 5ab997d..0000000
--- a/launcher/core/include/gameinstaller.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include
-#include
-
-class LauncherCore;
-class ProfileSettings;
-
-// TODO: convert to a nice signal/slots class like assetupdater
-void installGame(LauncherCore& launcher, ProfileSettings& profile, const std::function& returnFunc);
\ No newline at end of file
diff --git a/launcher/core/include/headline.h b/launcher/core/include/headline.h
deleted file mode 100644
index b8bbec5..0000000
--- a/launcher/core/include/headline.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include
-#include
-
-struct News {
- QDateTime date;
- QString id;
- QString tag;
- QString title;
- QUrl url;
-};
-
-struct Banner {
- QUrl link;
- QUrl bannerImage;
-};
-
-struct Headline {
- QList banner;
-
- QList news;
-
- QList pinned;
-
- QList topics;
-};
-
-class LauncherCore;
-
-void getHeadline(LauncherCore& core, const std::function& return_func);
\ No newline at end of file
diff --git a/launcher/core/include/launchercore.h b/launcher/core/include/launchercore.h
deleted file mode 100755
index 5d78f6a..0000000
--- a/launcher/core/include/launchercore.h
+++ /dev/null
@@ -1,266 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "squareboot.h"
-#include "steamapi.h"
-
-class SapphireLauncher;
-class SquareLauncher;
-class AssetUpdater;
-class Watchdog;
-
-enum class GameLicense {
- WindowsStandalone,
- WindowsSteam,
- macOS
-};
-
-enum class WineType {
- System,
- Custom,
- Builtin, // macos only
- XIVOnMac // macos only
-};
-
-enum class DalamudChannel {
- Stable,
- Staging,
- Net5
-};
-
-class ProfileSettings : public QObject {
- Q_OBJECT
- QML_ELEMENT
-public:
- QUuid uuid;
- QString name;
-
- // game
- int language = 1; // 1 is english, thats all i know
- QString gamePath, winePath, winePrefixPath;
- QString wineVersion;
- bool enableWatchdog = false;
-
- BootData* bootData;
- GameData* gameData;
-
- physis_Repositories repositories;
- const char* bootVersion;
-
- [[nodiscard]] bool isGameInstalled() const {
- return repositories.repositories_count > 0;
- }
-
- [[nodiscard]] bool isWineInstalled() const {
- return !wineVersion.isEmpty();
- }
-
-#if defined(Q_OS_MAC)
- WineType wineType = WineType::Builtin;
-#else
- WineType wineType = WineType::System;
-#endif
-
- bool useEsync = false, useGamescope = false, useGamemode = false;
- bool useDX9 = false;
- bool enableDXVKhud = false;
-
- struct GamescopeOptions {
- bool fullscreen = true;
- bool borderless = true;
- int width = 0;
- int height = 0;
- int refreshRate = 0;
- } gamescope;
-
- struct DalamudOptions {
- bool enabled = false;
- bool optOutOfMbCollection = false;
- DalamudChannel channel = DalamudChannel::Stable;
- } dalamud;
-
- // login
- bool encryptArguments = true;
- bool isSapphire = false;
- QString lobbyURL;
- bool rememberUsername = false, rememberPassword = false;
- bool rememberOTPSecret = false;
- bool useOneTimePassword = false;
- bool autoLogin = false;
-
- GameLicense license = GameLicense::WindowsStandalone;
- bool isFreeTrial = false;
-
- /*
- * Sets a value in the keychain. This function is asynchronous.
- */
- void setKeychainValue(const QString& key, const QString& value) const;
-
- /*
- * Retrieves a value from the keychain. This function is synchronous.
- */
- QString getKeychainValue(const QString& key) const;
-};
-
-struct AppSettings {
- bool closeWhenLaunched = true;
- bool showBanners = true;
- bool showNewsList = true;
-};
-
-class LoginInformation : public QObject {
- Q_OBJECT
- Q_PROPERTY(QString username MEMBER username)
- Q_PROPERTY(QString password MEMBER password)
- Q_PROPERTY(QString oneTimePassword MEMBER oneTimePassword)
- Q_PROPERTY(ProfileSettings* settings MEMBER settings)
- QML_ELEMENT
-public:
- ProfileSettings* settings = nullptr;
-
- 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 LauncherCore : public QObject {
- Q_OBJECT
- Q_PROPERTY(SquareBoot* squareBoot MEMBER squareBoot)
-public:
- explicit LauncherCore(bool isSteam);
-
- // used for qml only, TODO: move this to a dedicated factory
- Q_INVOKABLE LoginInformation* createNewLoginInfo() {
- return new LoginInformation();
- }
-
- QNetworkAccessManager* mgr;
-
- ProfileSettings& getProfile(int index);
-
- // used for qml only
- Q_INVOKABLE ProfileSettings* getProfileQML(int index) {
- return profileSettings[index];
- }
-
- int getProfileIndex(const QString& name);
- Q_INVOKABLE [[nodiscard]] QList profileList() const;
- int addProfile();
- int deleteProfile(const QString& name);
-
- /*
- * Begins the login process, and may call SquareBoot or SapphireLauncher depending on the profile type.
- * It's designed to be opaque as possible to the caller.
- *
- * The login process is asynchronous.
- */
- Q_INVOKABLE void login(LoginInformation* loginInformation);
-
- /*
- * Attempts to log into a profile without LoginInformation, which may or may not work depending on a combination of
- * the password failing, OTP not being available to auto-generate, among other things.
- *
- * The launcher will still warn the user about any possible errors, however the call site will need to check the
- * result to see whether they need to "reset" or show a failed state or not.
- */
- bool autoLogin(ProfileSettings& settings);
-
- /*
- * Launches the game using the provided authentication.
- */
- void launchGame(const ProfileSettings& settings, const LoginAuth& auth);
-
- /*
- * This just wraps it in wine if needed.
- */
- void launchExecutable(
- const ProfileSettings& settings,
- QProcess* process,
- const QStringList& args,
- bool isGame,
- bool needsRegistrySetup);
-
- void addRegistryKey(const ProfileSettings& settings, QString key, QString value, QString data);
-
- void buildRequest(const ProfileSettings& settings, QNetworkRequest& request);
- void setSSL(QNetworkRequest& request);
- void readInitialInformation();
- void readGameVersion();
- void readWineInfo(ProfileSettings& settings);
- void saveSettings();
-
- QSettings settings;
-
- SapphireLauncher* sapphireLauncher;
- SquareBoot* squareBoot;
- SquareLauncher* squareLauncher;
- AssetUpdater* assetUpdater;
- Watchdog* watchdog;
-
- bool gamescopeAvailable = false;
- bool gamemodeAvailable = false;
-
- AppSettings appSettings;
-
- QString dalamudVersion;
- int dalamudAssetVersion = -1;
- QString runtimeVersion;
-
- int defaultProfileIndex = 0;
-
- QVector expansionNames;
-
- bool isSteam = false;
-
-signals:
- void settingsChanged();
- void successfulLaunch();
- void gameClosed();
-
-private:
- /*
- * Begins the game executable, but calls to Dalamud if needed.
- */
- void beginGameExecutable(const ProfileSettings& settings, const LoginAuth& auth);
-
- /*
- * Starts a vanilla game session with no Dalamud injection.
- */
- void beginVanillaGame(const QString& gameExecutablePath, const ProfileSettings& profile, const LoginAuth& auth);
-
- /*
- * Starts a game session with Dalamud injected.
- */
- void beginDalamudGame(const QString& gameExecutablePath, const ProfileSettings& profile, const LoginAuth& auth);
-
- /*
- * Returns the game arguments needed to properly launch the game. This encrypts it too if needed, and it's already
- * joined!
- */
- QString getGameArgs(const ProfileSettings& profile, const LoginAuth& auth);
-
- bool checkIfInPath(const QString& program);
- void readGameData(ProfileSettings& profile);
-
- QString getDefaultGamePath();
- QString getDefaultWinePrefixPath();
-
- QVector profileSettings;
-
- SteamAPI* steamApi = nullptr;
-};
diff --git a/launcher/core/include/sapphirelauncher.h b/launcher/core/include/sapphirelauncher.h
deleted file mode 100644
index 76345c6..0000000
--- a/launcher/core/include/sapphirelauncher.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-
-#include
-
-#include "launchercore.h"
-
-class SapphireLauncher : QObject {
-public:
- explicit SapphireLauncher(LauncherCore& window);
-
- void login(const QString& lobbyUrl, const LoginInformation& info);
- void registerAccount(const QString& lobbyUrl, const LoginInformation& info);
-
-private:
- LauncherCore& window;
-};
\ No newline at end of file
diff --git a/launcher/core/include/squareboot.h b/launcher/core/include/squareboot.h
deleted file mode 100644
index 11feb0b..0000000
--- a/launcher/core/include/squareboot.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include "patcher.h"
-
-class SquareLauncher;
-class LauncherCore;
-struct LoginInformation;
-
-class SquareBoot : public QObject {
- Q_OBJECT
-public:
- SquareBoot(LauncherCore& window, SquareLauncher& launcher);
-
- Q_INVOKABLE void checkGateStatus(LoginInformation* info);
-
- void bootCheck(const LoginInformation& info);
-
-private:
- Patcher* patcher = nullptr;
-
- LauncherCore& window;
- SquareLauncher& launcher;
-};
\ No newline at end of file
diff --git a/launcher/core/include/squarelauncher.h b/launcher/core/include/squarelauncher.h
deleted file mode 100644
index 50e2b95..0000000
--- a/launcher/core/include/squarelauncher.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include "launchercore.h"
-#include "patcher.h"
-
-class SquareLauncher : public QObject {
- Q_OBJECT
-public:
- explicit SquareLauncher(LauncherCore& window);
-
- void getStored(const LoginInformation& info);
-
- void login(const LoginInformation& info, const QUrl& referer);
-
- void registerSession(const LoginInformation& info);
-
-private:
- QString getBootHash(const LoginInformation& info);
-
- Patcher* patcher = nullptr;
-
- QString stored, SID, username;
- LoginAuth auth;
-
- LauncherCore& window;
-};
diff --git a/launcher/core/include/steamapi.h b/launcher/core/include/steamapi.h
deleted file mode 100644
index efaac31..0000000
--- a/launcher/core/include/steamapi.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-
-class LauncherCore;
-
-class SteamAPI {
-public:
- explicit SteamAPI(LauncherCore& core);
-
- void setLauncherMode(bool isLauncher);
-
- [[nodiscard]] bool isDeck() const;
-
-private:
- LauncherCore& core;
-};
\ No newline at end of file
diff --git a/launcher/core/src/encryptedarg.cpp b/launcher/core/src/encryptedarg.cpp
deleted file mode 100644
index 55dbff4..0000000
--- a/launcher/core/src/encryptedarg.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#include "encryptedarg.h"
-
-#if defined(Q_OS_MAC)
- #include
- #include
-#endif
-
-#if defined(Q_OS_WIN)
- #include
-#endif
-
-#if defined(Q_OS_MAC)
-// taken from XIV-on-Mac, apparently Wine changed this?
-uint32_t TickCount() {
- struct mach_timebase_info timebase;
- mach_timebase_info(&timebase);
-
- auto machtime = mach_continuous_time();
- auto numer = uint64_t(timebase.numer);
- auto denom = uint64_t(timebase.denom);
- auto monotonic_time = machtime * numer / denom / 100;
- return monotonic_time / 10000;
-}
-#endif
-
-#if defined(Q_OS_LINUX)
-uint32_t TickCount() {
- struct timespec ts{};
-
- clock_gettime(CLOCK_MONOTONIC, &ts);
-
- return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
-}
-#endif
-
-#if defined(Q_OS_WIN)
-uint32_t TickCount() {
- return GetTickCount();
-}
-#endif
\ No newline at end of file
diff --git a/launcher/core/src/headline.cpp b/launcher/core/src/headline.cpp
deleted file mode 100644
index d8ee222..0000000
--- a/launcher/core/src/headline.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-#include "headline.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "launchercore.h"
-
-void getHeadline(LauncherCore& core, const std::function& return_func) {
- QUrlQuery query;
- query.addQueryItem("lang", "en-us");
- query.addQueryItem("media", "pcapp");
-
- QUrl url;
- url.setScheme("https");
- url.setHost("frontier.ffxiv.com");
- url.setPath("/news/headline.json");
- url.setQuery(query);
-
- auto request =
- QNetworkRequest(QString("%1&%2").arg(url.toString(), QString::number(QDateTime::currentMSecsSinceEpoch())));
-
- // TODO: really?
- core.buildRequest(core.getProfile(core.defaultProfileIndex), request);
-
- request.setRawHeader("Accept", "application/json, text/plain, */*");
- request.setRawHeader("Origin", "https://launcher.finalfantasyxiv.com");
- request.setRawHeader(
- "Referer",
- QString("https://launcher.finalfantasyxiv.com/v600/index.html?rc_lang=%1&time=%2")
- .arg("en-us", QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd-HH"))
- .toUtf8());
-
- auto reply = core.mgr->get(request);
- QObject::connect(reply, &QNetworkReply::finished, [=] {
- auto document = QJsonDocument::fromJson(reply->readAll());
-
- Headline headline;
-
- const auto parseNews = [](QJsonObject object) -> News {
- News news;
- news.date = QDateTime::fromString(object["date"].toString(), Qt::DateFormat::ISODate);
- news.id = object["id"].toString();
- news.tag = object["tag"].toString();
- news.title = object["title"].toString();
-
- if (object["url"].toString().isEmpty()) {
- news.url = QUrl(QString("https://na.finalfantasyxiv.com/lodestone/news/detail/%1").arg(news.id));
- } else {
- news.url = QUrl(object["url"].toString());
- }
-
- return news;
- };
-
- for (auto bannerObject : document.object()["banner"].toArray()) {
- Banner banner;
- banner.link = QUrl(bannerObject.toObject()["link"].toString());
- banner.bannerImage = QUrl(bannerObject.toObject()["lsb_banner"].toString());
-
- headline.banner.push_back(banner);
- }
-
- for (auto newsObject : document.object()["news"].toArray()) {
- auto news = parseNews(newsObject.toObject());
- headline.news.push_back(news);
- }
-
- for (auto pinnedObject : document.object()["pinned"].toArray()) {
- auto pinned = parseNews(pinnedObject.toObject());
- headline.pinned.push_back(pinned);
- }
-
- for (auto pinnedObject : document.object()["topics"].toArray()) {
- auto pinned = parseNews(pinnedObject.toObject());
- headline.topics.push_back(pinned);
- }
-
- return_func(headline);
- });
-}
\ No newline at end of file
diff --git a/launcher/core/src/launchercore.cpp b/launcher/core/src/launchercore.cpp
deleted file mode 100755
index 86de0a4..0000000
--- a/launcher/core/src/launchercore.cpp
+++ /dev/null
@@ -1,725 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#ifdef ENABLE_GAMEMODE
-#include
-#endif
-
-#include "assetupdater.h"
-#include "encryptedarg.h"
-#include "launchercore.h"
-#include "sapphirelauncher.h"
-#include "squareboot.h"
-#include "squarelauncher.h"
-
-#ifdef ENABLE_WATCHDOG
- #include "watchdog.h"
-#endif
-
-void LauncherCore::setSSL(QNetworkRequest& request) {
- QSslConfiguration config;
- config.setProtocol(QSsl::AnyProtocol);
- config.setPeerVerifyMode(QSslSocket::VerifyNone);
-
- request.setSslConfiguration(config);
-}
-
-void LauncherCore::buildRequest(const ProfileSettings& settings, QNetworkRequest& request) {
- setSSL(request);
-
- if (settings.license == GameLicense::macOS) {
- request.setHeader(QNetworkRequest::UserAgentHeader, "macSQEXAuthor/2.0.0(MacOSX; ja-jp)");
- } else {
- request.setHeader(
- QNetworkRequest::UserAgentHeader,
- QString("SQEXAuthor/2.0.0(Windows 6.2; ja-jp; %1)").arg(QString(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 LauncherCore::launchGame(const ProfileSettings& profile, const LoginAuth& auth) {
- steamApi->setLauncherMode(false);
-
-#ifdef ENABLE_WATCHDOG
- if (profile.enableWatchdog) {
- watchdog->launchGame(profile, auth);
- } else {
- beginGameExecutable(profile, auth);
- }
-#else
- beginGameExecutable(profile, auth);
-#endif
-}
-
-void LauncherCore::beginGameExecutable(const ProfileSettings& profile, const LoginAuth& auth) {
- QString gameExectuable;
- if(profile.useDX9) {
- gameExectuable = profile.gamePath + "/game/ffxiv.exe";
- } else {
- gameExectuable = profile.gamePath + "/game/ffxiv_dx11.exe";
- }
-
- if(profile.dalamud.enabled) {
- beginDalamudGame(gameExectuable, profile, auth);
- } else {
- beginVanillaGame(gameExectuable, profile, auth);
- }
-
- successfulLaunch();
-}
-
-void LauncherCore::beginVanillaGame(const QString& gameExecutablePath, const ProfileSettings& profile, const LoginAuth& auth) {
- auto gameProcess = new QProcess();
- gameProcess->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
-
- auto args = getGameArgs(profile, auth);
-
- launchExecutable(
- profile,
- gameProcess,
- {gameExecutablePath, args},
- true,
- true);
-}
-
-void LauncherCore::beginDalamudGame(const QString& gameExecutablePath, const ProfileSettings& profile, const LoginAuth& auth) {
- QString gamePath = gameExecutablePath;
- gamePath = "Z:" + gamePath.replace('/', '\\');
-
- QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
- dataDir = "Z:" + dataDir.replace('/', '\\');
-
- auto dalamudProcess = new QProcess();
-
- QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
- env.insert("DALAMUD_RUNTIME", dataDir + "\\DalamudRuntime");
-
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- env.insert("XL_WINEONLINUX", "true");
-#endif
- dalamudProcess->setProcessEnvironment(env);
-
- auto args = getGameArgs(profile, auth);
-
- launchExecutable(
- profile,
- dalamudProcess,
- {dataDir + "/Dalamud/" + "Dalamud.Injector.exe", "launch", "-m", "inject",
- "--game=" + gamePath,
- "--dalamud-configuration-path=" + dataDir + "\\dalamudConfig.json",
- "--dalamud-plugin-directory=" + dataDir + "\\installedPlugins",
- "--dalamud-asset-directory=" + dataDir + "\\DalamudAssets",
- "--dalamud-client-language=" + QString::number(profile.language),
- "--",
- args},
- true,
- true);
-}
-
-QString LauncherCore::getGameArgs(const ProfileSettings& profile, const LoginAuth& auth) {
- struct Argument {
- QString key, value;
- };
-
- QList gameArgs;
- gameArgs.push_back({"DEV.DataPathType", QString::number(1)});
- gameArgs.push_back({"DEV.UseSqPack", QString::number(1)});
-
- 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(profile.language)});
- gameArgs.push_back({"ver", profile.repositories.repositories[0].version});
-
- if (!auth.lobbyhost.isEmpty()) {
- gameArgs.push_back({"DEV.GMServerHost", auth.frontierHost});
- for (int i = 1; i < 9; i++) {
- gameArgs.push_back({QString("DEV.LobbyHost0%1").arg(QString::number(i)), auth.lobbyhost});
- gameArgs.push_back({QString("DEV.LobbyPort0%1").arg(QString::number(i)), QString::number(54994)});
- }
- }
-
- if (profile.license == GameLicense::WindowsSteam) {
- gameArgs.push_back({"IsSteam", "1"});
- }
-
- const QString argFormat = profile.encryptArguments ? " /%1 =%2" : " %1=%2";
-
- QString argJoined;
- for (const auto& arg : gameArgs) {
- argJoined += argFormat.arg(arg.key, arg.value);
- }
-
- return profile.encryptArguments ? encryptGameArg(argJoined) : argJoined;
-}
-
-void LauncherCore::launchExecutable(
- const ProfileSettings& profile,
- QProcess* process,
- const QStringList& args,
- bool isGame,
- bool needsRegistrySetup) {
- QList arguments;
- auto env = process->processEnvironment();
-
- if (needsRegistrySetup) {
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- if (profile.license == GameLicense::macOS) {
- addRegistryKey(profile, "HKEY_CURRENT_USER\\Software\\Wine", "HideWineExports", "0");
- } else {
- addRegistryKey(profile, "HKEY_CURRENT_USER\\Software\\Wine", "HideWineExports", "1");
- }
-#endif
- }
-
-#if defined(Q_OS_LINUX)
- if (isGame) {
- if (profile.useGamescope) {
- arguments.push_back("gamescope");
-
- if (profile.gamescope.fullscreen)
- arguments.push_back("-f");
-
- if (profile.gamescope.borderless)
- arguments.push_back("-b");
-
- if (profile.gamescope.width > 0)
- arguments.push_back("-w " + QString::number(profile.gamescope.width));
-
- if (profile.gamescope.height > 0)
- arguments.push_back("-h " + QString::number(profile.gamescope.height));
-
- if (profile.gamescope.refreshRate > 0)
- arguments.push_back("-r " + QString::number(profile.gamescope.refreshRate));
- }
- }
-#endif
-
-#if ENABLE_GAMEMODE
- if(isGame && profile.useGamemode) {
- gamemode_request_start();
- }
-#endif
-
-#if defined(Q_OS_LINUX)
- if (profile.useEsync) {
- env.insert("WINEESYNC", QString::number(1));
- env.insert("WINEFSYNC", QString::number(1));
- env.insert("WINEFSYNC_FUTEX2", QString::number(1));
- }
-#endif
-
-#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
- if(isSteam) {
- const QString steamDirectory = QProcessEnvironment::systemEnvironment().value("STEAM_COMPAT_CLIENT_INSTALL_PATH");
- const QString compatData = steamDirectory + "/steamapps/compatdata/39210"; // TODO: do these have to exist on the root steam folder?
- const QString protonPath = steamDirectory + "/steamapps/common/Proton 7.0";
-
- env.insert("PATH", protonPath + "/dist/bin:" + QProcessEnvironment::systemEnvironment().value("PATH"));
- env.insert("WINEDLLPATH", protonPath + "/dist/lib64/wine:" + protonPath + "/dist/lib/wine");
- env.insert("LD_LIBRARY_PATH", protonPath + "/dist/lib64:" + protonPath + "/dist/lib");
- env.insert("WINEPREFIX", compatData + "/pfx");
- env.insert("STEAM_COMPAT_CLIENT_INSTALL_PATH", steamDirectory);
- env.insert("STEAM_COMPAT_DATA_PATH", compatData);
-
- arguments.push_back(protonPath + "/proton");
- arguments.push_back("run");
- } else {
- env.insert("WINEPREFIX", profile.winePrefixPath);
-
- // XIV on Mac bundle their own Wine install directory, complete with libs etc
- if (profile.wineType == WineType::XIVOnMac) {
- // TODO: don't hardcode this
- QString xivLibPath = "/Applications/XIV on Mac.app/Contents/Resources/wine/lib:/Applications/XIV on "
- "Mac.app/Contents/Resources/MoltenVK/modern";
-
- env.insert("DYLD_FALLBACK_LIBRARY_PATH", xivLibPath);
- env.insert("DYLD_VERSIONED_LIBRARY_PATH", xivLibPath);
- env.insert("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1");
- env.insert("MVK_CONFIG_RESUME_LOST_DEVICE", "1");
- env.insert("MVK_ALLOW_METAL_FENCES", "1");
- env.insert("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
- }
-
- #if defined(FLATPAK)
- arguments.push_back("flatpak-spawn");
- arguments.push_back("--host");
- #endif
- arguments.push_back(profile.winePath);
- }
-#endif
-
- arguments.append(args);
-
- auto executable = arguments[0];
- arguments.removeFirst();
-
- if (isGame)
- process->setWorkingDirectory(profile.gamePath + "/game/");
-
- process->setProcessEnvironment(env);
-
- process->start(executable, arguments);
-}
-
-void LauncherCore::readInitialInformation() {
- defaultProfileIndex = settings.value("defaultProfile", 0).toInt();
-
- auto defaultAppSettings = AppSettings();
- appSettings.closeWhenLaunched = settings.value("closeWhenLaunched", defaultAppSettings.closeWhenLaunched).toBool();
- appSettings.showBanners = settings.value("showBanners", defaultAppSettings.showBanners).toBool();
- appSettings.showNewsList = settings.value("showNewsList", defaultAppSettings.showNewsList).toBool();
-
- gamescopeAvailable = checkIfInPath("gamescope");
- gamemodeAvailable = checkIfInPath("gamemoderun");
-
- const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
-
- const bool hasDalamud = QFile::exists(dataDir + "/Dalamud");
- if (hasDalamud) {
- if (QFile::exists(dataDir + "/Dalamud/Dalamud.deps.json")) {
- QFile depsJson(dataDir + "/Dalamud/Dalamud.deps.json");
- depsJson.open(QFile::ReadOnly);
- QJsonDocument doc = QJsonDocument::fromJson(depsJson.readAll());
-
- QString versionString;
- if (doc["targets"].toObject().contains(".NETCoreApp,Version=v5.0")) {
- versionString =
- doc["targets"].toObject()[".NETCoreApp,Version=v5.0"].toObject().keys().filter("Dalamud")[0];
- } else {
- versionString =
- doc["targets"].toObject()[".NETCoreApp,Version=v6.0"].toObject().keys().filter("Dalamud")[0];
- }
-
- dalamudVersion = versionString.remove("Dalamud/");
- }
-
- if (QFile::exists(dataDir + "/DalamudAssets/asset.ver")) {
- QFile assetJson(dataDir + "/DalamudAssets/asset.ver");
- assetJson.open(QFile::ReadOnly | QFile::Text);
-
- dalamudAssetVersion = QString(assetJson.readAll()).toInt();
- }
-
- if (QFile::exists(dataDir + "/DalamudRuntime/runtime.ver")) {
- QFile runtimeVer(dataDir + "/DalamudRuntime/runtime.ver");
- runtimeVer.open(QFile::ReadOnly | QFile::Text);
-
- runtimeVersion = QString(runtimeVer.readAll());
- }
- }
-
- auto profiles = settings.childGroups();
-
- // create the Default profile if it doesnt exist
- if (profiles.empty())
- profiles.append(QUuid::createUuid().toString(QUuid::StringFormat::WithoutBraces));
-
- profileSettings.resize(profiles.size());
-
- for (const auto& uuid : profiles) {
- auto profile = new ProfileSettings();
- profile->uuid = QUuid(uuid);
-
- settings.beginGroup(uuid);
-
- profile->name = settings.value("name", "Default").toString();
-
- if (settings.contains("gamePath") && settings.value("gamePath").canConvert() &&
- !settings.value("gamePath").toString().isEmpty()) {
- profile->gamePath = settings.value("gamePath").toString();
- } else {
- profile->gamePath = getDefaultGamePath();
- }
-
- if (settings.contains("winePrefixPath") && settings.value("winePrefixPath").canConvert() &&
- !settings.value("winePrefixPath").toString().isEmpty()) {
- profile->winePrefixPath = settings.value("winePrefixPath").toString();
- } else {
- profile->winePrefixPath = getDefaultWinePrefixPath();
- }
-
- if (settings.contains("winePath") && settings.value("winePath").canConvert() &&
- !settings.value("winePath").toString().isEmpty()) {
- profile->winePath = settings.value("winePath").toString();
- }
-
- ProfileSettings defaultSettings;
-
- // login
- profile->encryptArguments = settings.value("encryptArguments", defaultSettings.encryptArguments).toBool();
- profile->isSapphire = settings.value("isSapphire", defaultSettings.isSapphire).toBool();
- profile->lobbyURL = settings.value("lobbyURL", defaultSettings.lobbyURL).toString();
- profile->rememberUsername = settings.value("rememberUsername", defaultSettings.rememberUsername).toBool();
- profile->rememberPassword = settings.value("rememberPassword", defaultSettings.rememberPassword).toBool();
- profile->rememberOTPSecret = settings.value("rememberOTPSecret", defaultSettings.rememberOTPSecret).toBool();
- profile->useOneTimePassword = settings.value("useOneTimePassword", defaultSettings.useOneTimePassword).toBool();
- profile->license = (GameLicense)settings.value("license", (int)defaultSettings.license).toInt();
- profile->isFreeTrial = settings.value("isFreeTrial", defaultSettings.isFreeTrial).toBool();
- profile->autoLogin = settings.value("autoLogin", defaultSettings.autoLogin).toBool();
-
- profile->useDX9 = settings.value("useDX9", defaultSettings.useDX9).toBool();
-
- // wine
- profile->wineType = (WineType)settings.value("wineType", (int)defaultSettings.wineType).toInt();
- profile->useEsync = settings.value("useEsync", defaultSettings.useEsync).toBool();
-
- readWineInfo(*profile);
-
- if (gamescopeAvailable)
- profile->useGamescope = settings.value("useGamescope", defaultSettings.useGamescope).toBool();
-
- if (gamemodeAvailable)
- profile->useGamemode = settings.value("useGamemode", defaultSettings.useGamemode).toBool();
-
- profile->enableDXVKhud = settings.value("enableDXVKhud", defaultSettings.enableDXVKhud).toBool();
-
- profile->enableWatchdog = settings.value("enableWatchdog", defaultSettings.enableWatchdog).toBool();
-
- // gamescope
- profile->gamescope.borderless =
- settings.value("gamescopeBorderless", defaultSettings.gamescope.borderless).toBool();
- profile->gamescope.width = settings.value("gamescopeWidth", defaultSettings.gamescope.width).toInt();
- profile->gamescope.height = settings.value("gamescopeHeight", defaultSettings.gamescope.height).toInt();
- profile->gamescope.refreshRate =
- settings.value("gamescopeRefreshRate", defaultSettings.gamescope.refreshRate).toInt();
-
- profile->dalamud.enabled = settings.value("enableDalamud", defaultSettings.dalamud.enabled).toBool();
- profile->dalamud.optOutOfMbCollection =
- settings.value("dalamudOptOut", defaultSettings.dalamud.optOutOfMbCollection).toBool();
- profile->dalamud.channel =
- (DalamudChannel)settings.value("dalamudChannel", (int)defaultSettings.dalamud.channel).toInt();
-
- profileSettings[settings.value("index").toInt()] = profile;
-
- settings.endGroup();
- }
-
- readGameVersion();
-}
-
-void LauncherCore::readWineInfo(ProfileSettings& profile) {
-#if defined(Q_OS_MAC)
- switch (profile.wineType) {
- case WineType::System: // system wine
- profile.winePath = "/usr/local/bin/wine64";
- break;
- case WineType::Custom: // custom path
- profile.winePath = profile.winePath;
- break;
- case WineType::Builtin: // ffxiv built-in (for mac users)
- profile.winePath = "/Applications/FINAL FANTASY XIV "
- "ONLINE.app/Contents/SharedSupport/finalfantasyxiv/FINAL FANTASY XIV ONLINE/wine";
- break;
- case WineType::XIVOnMac:
- profile.winePath = "/Applications/XIV on Mac.app/Contents/Resources/wine/bin/wine64";
- break;
- }
-#endif
-
-#if defined(Q_OS_LINUX)
- switch (profile.wineType) {
- case WineType::System: // system wine (should be in $PATH)
- profile.winePath = "/usr/bin/wine";
- break;
- case WineType::Custom: // custom pth
- profile.winePath = profile.winePath;
- break;
- }
-#endif
-
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- auto wineProcess = new QProcess(this);
- wineProcess->setProcessChannelMode(QProcess::MergedChannels);
-
- connect(wineProcess, &QProcess::readyRead, this, [wineProcess, &profile] {
- profile.wineVersion = wineProcess->readAllStandardOutput().trimmed();
- });
-
- launchExecutable(profile, wineProcess, {"--version"}, false, false);
-
- wineProcess->waitForFinished();
-#endif
-}
-
-void LauncherCore::readGameVersion() {
- for (auto& profile : profileSettings) {
- profile->gameData = physis_gamedata_initialize((profile->gamePath + "/game").toStdString().c_str());
- profile->bootData = physis_bootdata_initialize((profile->gamePath + "/boot").toStdString().c_str());
-
- if(profile->bootData != nullptr) {
- profile->bootVersion = physis_bootdata_get_version(profile->bootData);
- }
-
- if(profile->gameData != nullptr) {
- profile->repositories = physis_gamedata_get_repositories(profile->gameData);
- readGameData(*profile);
- }
- }
-}
-
-LauncherCore::LauncherCore(bool isSteam)
- : settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::applicationName()), isSteam(isSteam) {
- mgr = new QNetworkAccessManager();
- sapphireLauncher = new SapphireLauncher(*this);
- squareLauncher = new SquareLauncher(*this);
- squareBoot = new SquareBoot(*this, *squareLauncher);
- assetUpdater = new AssetUpdater(*this);
- steamApi = new SteamAPI(*this);
-
-#ifdef ENABLE_WATCHDOG
- watchdog = new Watchdog(*this);
-#endif
-
- readInitialInformation();
-
- steamApi->setLauncherMode(true);
-}
-
-ProfileSettings& LauncherCore::getProfile(int index) {
- return *profileSettings[index];
-}
-
-int LauncherCore::getProfileIndex(const QString& name) {
- for (int i = 0; i < profileSettings.size(); i++) {
- if (profileSettings[i]->name == name)
- return i;
- }
-
- return -1;
-}
-
-QList LauncherCore::profileList() const {
- QList list;
- for (auto profile : profileSettings) {
- list.append(profile->name);
- }
-
- return list;
-}
-
-int LauncherCore::addProfile() {
- auto newProfile = new ProfileSettings();
- newProfile->uuid = QUuid::createUuid();
- newProfile->name = "New Profile";
-
- readWineInfo(*newProfile);
-
- newProfile->gamePath = getDefaultGamePath();
- newProfile->winePrefixPath = getDefaultWinePrefixPath();
-
- profileSettings.append(newProfile);
-
- settingsChanged();
-
- return profileSettings.size() - 1;
-}
-
-int LauncherCore::deleteProfile(const QString& name) {
- int index = 0;
- for (int i = 0; i < profileSettings.size(); i++) {
- if (profileSettings[i]->name == name)
- index = i;
- }
-
- // remove group so it doesnt stay
- settings.beginGroup(profileSettings[index]->uuid.toString(QUuid::StringFormat::WithoutBraces));
- settings.remove("");
- settings.endGroup();
-
- profileSettings.removeAt(index);
-
- return index - 1;
-}
-
-void LauncherCore::saveSettings() {
- settings.setValue("defaultProfile", defaultProfileIndex);
- settings.setValue("closeWhenLaunched", appSettings.closeWhenLaunched);
- settings.setValue("showBanners", appSettings.showBanners);
- settings.setValue("showNewsList", appSettings.showNewsList);
-
- for (int i = 0; i < profileSettings.size(); i++) {
- const auto& profile = profileSettings[i];
-
- settings.beginGroup(profile->uuid.toString(QUuid::StringFormat::WithoutBraces));
-
- settings.setValue("name", profile->name);
- settings.setValue("index", i);
-
- // game
- settings.setValue("useDX9", profile->useDX9);
- settings.setValue("gamePath", profile->gamePath);
-
- // wine
- settings.setValue("wineType", (int)profile->wineType);
- settings.setValue("winePath", profile->winePath);
- settings.setValue("winePrefixPath", profile->winePrefixPath);
-
- settings.setValue("useEsync", profile->useEsync);
- settings.setValue("useGamescope", profile->useGamescope);
- settings.setValue("useGamemode", profile->useGamemode);
-
- // gamescope
- settings.setValue("gamescopeFullscreen", profile->gamescope.fullscreen);
- settings.setValue("gamescopeBorderless", profile->gamescope.borderless);
- settings.setValue("gamescopeWidth", profile->gamescope.width);
- settings.setValue("gamescopeHeight", profile->gamescope.height);
- settings.setValue("gamescopeRefreshRate", profile->gamescope.refreshRate);
-
- // login
- settings.setValue("encryptArguments", profile->encryptArguments);
- settings.setValue("isSapphire", profile->isSapphire);
- settings.setValue("lobbyURL", profile->lobbyURL);
- settings.setValue("rememberUsername", profile->rememberUsername);
- settings.setValue("rememberPassword", profile->rememberPassword);
- settings.setValue("rememberOTPSecret", profile->rememberOTPSecret);
- settings.setValue("useOneTimePassword", profile->useOneTimePassword);
- settings.setValue("license", (int)profile->license);
- settings.setValue("isFreeTrial", profile->isFreeTrial);
- settings.setValue("autoLogin", profile->autoLogin);
-
- settings.setValue("enableDalamud", profile->dalamud.enabled);
- settings.setValue("dalamudOptOut", profile->dalamud.optOutOfMbCollection);
- settings.setValue("dalamudChannel", (int)profile->dalamud.channel);
- settings.setValue("enableWatchdog", profile->enableWatchdog);
-
- settings.endGroup();
- }
-}
-
-bool LauncherCore::checkIfInPath(const QString& program) {
- // TODO: also check /usr/local/bin, /bin32 etc (basically read $PATH)
- const QString directory = "/usr/bin";
-
- QFileInfo fileInfo(directory + "/" + program);
-
- return fileInfo.exists() && fileInfo.isFile();
-}
-
-QString LauncherCore::getDefaultWinePrefixPath() {
-#if defined(Q_OS_MACOS)
- return QDir::homePath() + "/Library/Application Support/FINAL FANTASY XIV ONLINE/Bottles/published_Final_Fantasy";
-#endif
-
-#if defined(Q_OS_LINUX)
- return QDir::homePath() + "/.wine";
-#endif
-
- return "";
-}
-
-QString LauncherCore::getDefaultGamePath() {
-#if defined(Q_OS_WIN)
- return "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn";
-#endif
-
-#if defined(Q_OS_MAC)
- return 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)
- return QDir::homePath() + "/.wine/drive_c/Program Files (x86)/SquareEnix/FINAL FANTASY XIV - A Realm Reborn";
-#endif
-}
-
-void LauncherCore::addRegistryKey(const ProfileSettings& settings, QString key, QString value, QString data) {
- auto process = new QProcess(this);
- process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
- launchExecutable(settings, process, {"reg", "add", std::move(key), "/v", std::move(value), "/d", std::move(data), "/f"}, false, false);
-}
-
-void LauncherCore::readGameData(ProfileSettings& profile) {
- physis_EXH* exh = physis_gamedata_read_excel_sheet_header(profile.gameData, "ExVersion");
- if (exh != nullptr) {
- physis_EXD exd = physis_gamedata_read_excel_sheet(profile.gameData, "ExVersion", exh, Language::English, 0);
-
- for (int i = 0; i < exd.row_count; i++) {
- expansionNames.push_back(exd.row_data[i].column_data[0].string._0);
- }
-
- physis_gamedata_free_sheet(exd);
- physis_gamedata_free_sheet_header(exh);
- }
-}
-
-void LauncherCore::login(LoginInformation* loginInformation) {
- if (loginInformation->settings->isSapphire) {
- sapphireLauncher->login(loginInformation->settings->lobbyURL, *loginInformation);
- } else {
- squareBoot->checkGateStatus(loginInformation);
- }
-}
-
-bool LauncherCore::autoLogin(ProfileSettings& profile) {
- QString username = profile.getKeychainValue("username");
- QString password = profile.getKeychainValue("password");
- QString otpSecret = profile.getKeychainValue("otpsecret");
-
- auto info = new LoginInformation();
- info->settings = &profile;
- info->username = username;
- info->password = password;
-
- if(profile.useOneTimePassword && !profile.rememberOTPSecret)
- return false;
-
- if(profile.useOneTimePassword && profile.rememberOTPSecret) {
- // generate otp
- char* totp = get_totp (otpSecret.toStdString().c_str(), 6, 30, SHA1, nullptr);
- info->oneTimePassword = totp;
- free (totp);
- }
-
- // TODO: when login fails, we need some way to propagate this back? or not?
- login(info);
-
- return true;
-}
-
-void ProfileSettings::setKeychainValue(const QString& key, const QString& value) const {
- auto job = new QKeychain::WritePasswordJob("Astra");
- job->setTextData(value);
- job->setKey(name + "-" + key);
- job->start();
-}
-
-QString ProfileSettings::getKeychainValue(const QString& key) const {
- auto loop = new QEventLoop();
-
- auto job = new QKeychain::ReadPasswordJob("Astra");
- job->setKey(name + "-" + key);
- job->start();
-
- QString value;
-
- QObject::connect(
- job, &QKeychain::ReadPasswordJob::finished, [loop, job, &value](QKeychain::Job* j) {
- value = job->textData();
- loop->quit();
- });
-
- loop->exec();
-
- return value;
-}
diff --git a/launcher/desktop/CMakeLists.txt b/launcher/desktop/CMakeLists.txt
deleted file mode 100644
index 01088d4..0000000
--- a/launcher/desktop/CMakeLists.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-set(HEADERS
- include/aboutwindow.h
- include/bannerwidget.h
- include/desktopinterface.h
- include/gamescopesettingswindow.h
- include/launcherwindow.h
- include/settingswindow.h
- include/autologinwindow.h)
-
-set(SRC
- src/aboutwindow.cpp
- src/bannerwidget.cpp
- src/desktopinterface.cpp
- src/gamescopesettingswindow.cpp
- src/launcherwindow.cpp
- src/settingswindow.cpp
- src/autologinwindow.cpp include/virtualwindow.h src/virtualwindow.cpp src/virtualdialog.cpp include/virtualdialog.h)
-
-add_library(astra_desktop STATIC ${HEADERS} ${SRC})
-target_include_directories(astra_desktop PUBLIC include)
-target_link_libraries(astra_desktop PUBLIC
- astra_core
- Qt5::Core
- Qt5::Widgets
- Qt5::Network)
\ No newline at end of file
diff --git a/launcher/desktop/include/aboutwindow.h b/launcher/desktop/include/aboutwindow.h
deleted file mode 100644
index 85f067a..0000000
--- a/launcher/desktop/include/aboutwindow.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-
-#include "virtualdialog.h"
-
-class AboutWindow : public VirtualDialog {
-public:
- explicit AboutWindow(DesktopInterface& interface, QWidget* widget = nullptr);
-};
\ No newline at end of file
diff --git a/launcher/desktop/include/autologinwindow.h b/launcher/desktop/include/autologinwindow.h
deleted file mode 100644
index f358029..0000000
--- a/launcher/desktop/include/autologinwindow.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-
-#include "virtualdialog.h"
-
-class LauncherCore;
-class LauncherWindow;
-struct ProfileSettings;
-
-class AutoLoginWindow : public VirtualDialog {
- Q_OBJECT
-public:
- AutoLoginWindow(DesktopInterface& interface, ProfileSettings& settings, LauncherCore& core, QWidget* parent = nullptr);
-
-signals:
- void loginCanceled();
-};
diff --git a/launcher/desktop/include/bannerwidget.h b/launcher/desktop/include/bannerwidget.h
deleted file mode 100644
index 2bafa9d..0000000
--- a/launcher/desktop/include/bannerwidget.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include
-#include
-
-class BannerWidget : public QLabel {
-public:
- BannerWidget();
-
- void setUrl(QUrl url);
-
-protected:
- void mousePressEvent(QMouseEvent* event) override;
-
-private:
- QUrl url;
-};
\ No newline at end of file
diff --git a/launcher/desktop/include/desktopinterface.h b/launcher/desktop/include/desktopinterface.h
deleted file mode 100644
index 19af55a..0000000
--- a/launcher/desktop/include/desktopinterface.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#pragma once
-
-#include
-#include
-
-#include "launcherwindow.h"
-#include "autologinwindow.h"
-#include "virtualdialog.h"
-
-/*
- * The desktop, mouse and keyboard-driven interface for Astra. Primarily meant
- * for regular desktop usage.
- */
-class DesktopInterface {
-public:
- explicit DesktopInterface(LauncherCore& core);
-
- void addWindow(VirtualWindow* window);
- void addDialog(VirtualDialog* dialog);
-
- bool oneWindow = false;
- bool isSteamDeck = false;
-
-private:
- QMdiArea* mdiArea = nullptr;
- QMainWindow* mdiWindow = nullptr;
-
- LauncherWindow* window = nullptr;
- AutoLoginWindow* autoLoginWindow = nullptr;
-};
\ No newline at end of file
diff --git a/launcher/desktop/include/gamescopesettingswindow.h b/launcher/desktop/include/gamescopesettingswindow.h
deleted file mode 100644
index 5cc6c47..0000000
--- a/launcher/desktop/include/gamescopesettingswindow.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "virtualdialog.h"
-
-class LauncherCore;
-class LauncherWindow;
-struct ProfileSettings;
-
-class GamescopeSettingsWindow : public VirtualDialog {
-public:
- GamescopeSettingsWindow(DesktopInterface& interface, ProfileSettings& settings, LauncherCore& core, QWidget* parent = nullptr);
-};
diff --git a/launcher/desktop/include/launcherwindow.h b/launcher/desktop/include/launcherwindow.h
deleted file mode 100644
index 973e5b3..0000000
--- a/launcher/desktop/include/launcherwindow.h
+++ /dev/null
@@ -1,67 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "headline.h"
-#include "launchercore.h"
-#include "virtualwindow.h"
-
-class DesktopInterface;
-
-class LauncherWindow : public VirtualWindow {
- Q_OBJECT
-public:
- explicit LauncherWindow(DesktopInterface& interface, LauncherCore& new_headline, QWidget* parent = nullptr);
-
- ProfileSettings& currentProfile();
-
- void openPath(const QString& path);
-
-public slots:
- void reloadControls();
-
-private:
- void reloadNews();
-
- LauncherCore& core;
-
- Headline headline;
-
- bool currentlyReloadingControls = false;
-
- QGridLayout* layout;
- QFormLayout* loginLayout;
-
- QScrollArea* bannerScrollArea;
- QWidget* bannerParentWidget;
- QHBoxLayout* bannerLayout;
- QTreeWidget* newsListView;
- QTimer* bannerTimer = nullptr;
- int currentBanner = 0;
-
- std::vector bannerWidgets;
-
- QAction* launchOfficial;
- QAction* launchSysInfo;
- QAction* launchCfgBackup;
- QAction* openGameDir;
-
- QComboBox* profileSelect;
- QLineEdit *usernameEdit, *passwordEdit;
- QLineEdit* otpEdit;
- QCheckBox *rememberUsernameBox, *rememberPasswordBox;
- QPushButton *loginButton, *registerButton;
-
-#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
- QAction* wineCfg;
-#endif
-
- DesktopInterface& interface;
-};
\ No newline at end of file
diff --git a/launcher/desktop/include/settingswindow.h b/launcher/desktop/include/settingswindow.h
deleted file mode 100644
index d0b9d50..0000000
--- a/launcher/desktop/include/settingswindow.h
+++ /dev/null
@@ -1,89 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "virtualdialog.h"
-
-class LauncherCore;
-class LauncherWindow;
-struct ProfileSettings;
-
-class SettingsWindow : public VirtualDialog {
-public:
- SettingsWindow(DesktopInterface& interface, int defaultTab, LauncherWindow& window, LauncherCore& core, QWidget* parent = nullptr);
-
-public slots:
- void reloadControls();
-
-private:
- void setupAccountsTab(QGridLayout& layout);
-
- // profile specific tabs
- void setupGameTab(QFormLayout& layout);
- void setupLoginTab(QFormLayout& layout);
- void setupWineTab(QFormLayout& layout);
- void setupDalamudTab(QFormLayout& layout);
-
- ProfileSettings& getCurrentProfile();
-
- QListWidget* profileWidget = nullptr;
- QPushButton* deleteAccountButton = nullptr;
-
- QListWidget* accountWidget = nullptr;
- QPushButton* removeAccountButton = nullptr;
-
- // general
- QCheckBox* closeWhenLaunched = nullptr;
- QCheckBox* showBanner = nullptr;
- QCheckBox* showNewsList = nullptr;
-
- // game
- QLineEdit* nameEdit = nullptr;
- QComboBox* directXCombo = nullptr;
- QLabel* currentGameDirectory = nullptr;
- QLabel* expansionVersionLabel = nullptr;
- QPushButton* gameDirectoryButton = nullptr;
-
- // wine
- QComboBox* wineTypeCombo;
- QPushButton* selectWineButton;
- QLabel* winePathLabel;
- QLabel* winePrefixDirectory;
- QPushButton* configureGamescopeButton;
- QLabel* wineVersionLabel;
-
- QCheckBox *useGamescope, *useEsync, *useGamemode;
- QCheckBox* enableWatchdog;
-
- // login
- QCheckBox* encryptArgumentsBox = nullptr;
- QComboBox* serverType = nullptr;
- QLineEdit* lobbyServerURL = nullptr;
- QCheckBox *rememberUsernameBox = nullptr, *rememberPasswordBox = nullptr, *rememberOTPSecretBox = nullptr;
- QPushButton* otpSecretButton = nullptr;
- QComboBox* gameLicenseBox = nullptr;
- QCheckBox* freeTrialBox = nullptr;
- QCheckBox* useOneTimePassword = nullptr;
- QCheckBox* autoLoginBox = nullptr;
-
- // dalamud
- QCheckBox* enableDalamudBox = nullptr;
- QLabel* dalamudVersionLabel = nullptr;
- QLabel* dalamudAssetVersionLabel = nullptr;
- QCheckBox* dalamudOptOutBox = nullptr;
- QComboBox* dalamudChannel = nullptr;
-
- bool currentlyReloadingControls = false;
-
- LauncherWindow& window;
- LauncherCore& core;
-
- DesktopInterface& interface;
-};
diff --git a/launcher/desktop/include/virtualdialog.h b/launcher/desktop/include/virtualdialog.h
deleted file mode 100644
index 9473530..0000000
--- a/launcher/desktop/include/virtualdialog.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-class DesktopInterface;
-
-class VirtualDialog : public QObject {
- Q_OBJECT
-public:
- explicit VirtualDialog(DesktopInterface& interface, QWidget* parent = nullptr);
-
- void setWindowTitle(const QString& title);
- void show();
- void hide();
- void close();
- void setWindowModality(Qt::WindowModality modality);
- void setLayout(QLayout* layout);
-
- QWidget* getRootWidget();
-
- QMdiSubWindow* mdi_window = nullptr;
- QDialog* normal_dialog = nullptr;
-
-private:
- DesktopInterface& interface;
-};
\ No newline at end of file
diff --git a/launcher/desktop/include/virtualwindow.h b/launcher/desktop/include/virtualwindow.h
deleted file mode 100644
index c2ab01b..0000000
--- a/launcher/desktop/include/virtualwindow.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-class DesktopInterface;
-
-class VirtualWindow : public QObject {
- Q_OBJECT
-public:
- explicit VirtualWindow(DesktopInterface& interface, QWidget* parent = nullptr);
-
- void setWindowTitle(const QString& title);
- void setCentralWidget(QWidget* widget);
- void show();
- void showMaximized();
- void hide();
-
- QMenuBar* menuBar();
-
- QWidget* getRootWidget();
-
- QMdiSubWindow* mdi_window = nullptr;
- QMainWindow* normal_window = nullptr;
-
-private:
- DesktopInterface& interface;
-};
\ No newline at end of file
diff --git a/launcher/desktop/src/aboutwindow.cpp b/launcher/desktop/src/aboutwindow.cpp
deleted file mode 100644
index 26f59af..0000000
--- a/launcher/desktop/src/aboutwindow.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-#include "aboutwindow.h"
-
-#include
-#include
-#include
-#include
-
-#include "config.h"
-#include "license.h"
-
-AboutWindow::AboutWindow(DesktopInterface& interface, QWidget* widget) : VirtualDialog(interface, widget) {
- setWindowTitle("About");
- setWindowModality(Qt::WindowModality::ApplicationModal);
-
- auto mainLayout = new QVBoxLayout();
- setLayout(mainLayout);
-
- auto mainLabel = new QLabel();
- mainLabel->setText(QString("Astra
\nVersion %1").arg(version));
- mainLayout->addWidget(mainLabel);
-
- auto aboutWidget = new QWidget();
- auto aboutLayout = new QVBoxLayout();
- aboutWidget->setLayout(aboutLayout);
-
- auto aboutLabel = new QLabel();
- aboutLabel->setText("Cross-platform FFXIV launcher");
- aboutLayout->addWidget(aboutLabel);
-
- auto websiteLabel = new QLabel();
- websiteLabel->setText("https://xiv.zone/astra");
- websiteLabel->setOpenExternalLinks(true);
- aboutLayout->addWidget(websiteLabel);
-
- auto licenseLabel = new QLabel();
- licenseLabel->setText("License: GNU General Public License Version 3");
- connect(licenseLabel, &QLabel::linkActivated, [&interface] {
- auto licenseDialog = new VirtualDialog(interface);
- licenseDialog->setWindowTitle("License Agreement");
-
- auto layout = new QVBoxLayout();
- licenseDialog->setLayout(layout);
-
- auto licenseEdit = new QPlainTextEdit();
- licenseEdit->setPlainText(license);
- licenseEdit->setReadOnly(true);
- layout->addWidget(licenseEdit);
-
- licenseDialog->show();
- });
- aboutLayout->addWidget(licenseLabel);
-
- aboutLayout->addStretch();
-
- auto authorsWidget = new QWidget();
- auto authorsLayout = new QVBoxLayout();
- authorsWidget->setLayout(authorsLayout);
-
- auto authorNameLabel = new QLabel();
- authorNameLabel->setText("Joshua Goins");
-
- QFont boldFont = authorNameLabel->font();
- boldFont.setBold(true);
- authorNameLabel->setFont(boldFont);
-
- authorsLayout->addWidget(authorNameLabel);
-
- auto authorRoleLabel = new QLabel();
- authorRoleLabel->setText("Maintainer");
- authorsLayout->addWidget(authorRoleLabel);
-
- authorsLayout->addStretch();
-
- auto tabWidget = new QTabWidget();
- tabWidget->addTab(aboutWidget, "About");
- tabWidget->addTab(authorsWidget, "Authors");
- mainLayout->addWidget(tabWidget);
-}
\ No newline at end of file
diff --git a/launcher/desktop/src/autologinwindow.cpp b/launcher/desktop/src/autologinwindow.cpp
deleted file mode 100644
index 33df08c..0000000
--- a/launcher/desktop/src/autologinwindow.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#include "autologinwindow.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "launchercore.h"
-#include "launcherwindow.h"
-#include "sapphirelauncher.h"
-
-AutoLoginWindow::AutoLoginWindow(DesktopInterface& interface, ProfileSettings& profile, LauncherCore& core, QWidget* parent)
- : VirtualDialog(interface, parent) {
- setWindowTitle("Auto Login");
- setWindowModality(Qt::WindowModality::ApplicationModal);
-
- auto mainLayout = new QFormLayout();
- setLayout(mainLayout);
-
- auto label = new QLabel("Currently logging in...");
- mainLayout->addWidget(label);
-
- auto cancelButton = new QPushButton("Cancel");
- connect(cancelButton, &QPushButton::clicked, this, &AutoLoginWindow::loginCanceled);
- mainLayout->addWidget(cancelButton);
-
- auto autologinTimer = new QTimer();
-
- connect(autologinTimer, &QTimer::timeout, [&, this, autologinTimer] {
- core.autoLogin(profile);
- });
-
- connect(this, &AutoLoginWindow::loginCanceled, [autologinTimer] {
- autologinTimer->stop();
- });
-
- connect(&core, &LauncherCore::successfulLaunch, [this, autologinTimer] {
- close();
- autologinTimer->stop();
- });
-
- autologinTimer->start(5000);
-}
\ No newline at end of file
diff --git a/launcher/desktop/src/bannerwidget.cpp b/launcher/desktop/src/bannerwidget.cpp
deleted file mode 100644
index 65deafc..0000000
--- a/launcher/desktop/src/bannerwidget.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "bannerwidget.h"
-
-#include
-#include
-#include
-
-BannerWidget::BannerWidget() : QLabel() {
- setCursor(Qt::CursorShape::PointingHandCursor);
-}
-
-void BannerWidget::mousePressEvent(QMouseEvent* event) {
- qDebug() << "Clicked!";
- QDesktopServices::openUrl(url);
-}
-
-void BannerWidget::setUrl(QUrl newUrl) {
- this->url = std::move(newUrl);
-}
diff --git a/launcher/desktop/src/desktopinterface.cpp b/launcher/desktop/src/desktopinterface.cpp
deleted file mode 100644
index e7718da..0000000
--- a/launcher/desktop/src/desktopinterface.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-#include "desktopinterface.h"
-#include "autologinwindow.h"
-#include "gameinstaller.h"
-
-DesktopInterface::DesktopInterface(LauncherCore& core) {
- if(oneWindow) {
- mdiArea = new QMdiArea();
- mdiWindow = new QMainWindow();
-
- if(isSteamDeck) {
- mdiWindow->setWindowFlag(Qt::FramelessWindowHint);
- mdiWindow->setFixedSize(1280, 800);
- }
-
- mdiWindow->setWindowTitle("Combined Interface");
- mdiWindow->setCentralWidget(mdiArea);
- mdiWindow->show();
- }
-
- window = new LauncherWindow(*this, core);
-
- auto& defaultProfile = core.getProfile(core.defaultProfileIndex);
-
- if (!defaultProfile.isGameInstalled()) {
- auto messageBox = new QMessageBox();
- messageBox->setIcon(QMessageBox::Icon::Question);
- messageBox->setText("No Game Found");
- messageBox->setInformativeText("FFXIV is not installed. Would you like to install it now?");
-
- QString detailedText =
- QString("Astra will install FFXIV for you at '%1'").arg(core.getProfile(core.defaultProfileIndex).gamePath);
- detailedText.append(
- "\n\nIf you do not wish to install it to this location, please set it in your default profile first.");
-
- messageBox->setDetailedText(detailedText);
- messageBox->setWindowModality(Qt::WindowModal);
-
- auto installButton = messageBox->addButton("Install Game", QMessageBox::YesRole);
- QObject::connect(installButton, &QPushButton::clicked, [&core, messageBox] {
- installGame(core, core.getProfile(core.defaultProfileIndex), [messageBox, &core] {
- core.readGameVersion();
-
- messageBox->close();
- });
- });
-
- messageBox->addButton(QMessageBox::StandardButton::No);
- messageBox->setDefaultButton(installButton);
-
- messageBox->exec();
- }
-
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- if (!core.isSteam && !defaultProfile.isWineInstalled()) {
- auto messageBox = new QMessageBox();
- messageBox->setIcon(QMessageBox::Icon::Critical);
- messageBox->setAttribute(Qt::WA_DeleteOnClose);
- messageBox->setText("No Wine Found");
- messageBox->setInformativeText("Wine is not installed but is required to FFXIV on this operating system.");
- //messageBox->setWindowModality(Qt::WindowModal);
-
- messageBox->addButton(QMessageBox::StandardButton::Ok);
- messageBox->setDefaultButton(QMessageBox::StandardButton::Ok);
-
- messageBox->show();
- }
-#endif
-
- if(defaultProfile.autoLogin) {
- autoLoginWindow = new AutoLoginWindow(*this, defaultProfile, core);
- autoLoginWindow->show();
-
- QObject::connect(autoLoginWindow, &AutoLoginWindow::loginCanceled,[=] {
- autoLoginWindow->hide();
- window->show();
- });
- } else {
- if(oneWindow) {
- window->showMaximized();
- } else {
- window->show();
- }
- }
-}
-
-void DesktopInterface::addWindow(VirtualWindow* window) {
- if(oneWindow) {
- mdiArea->addSubWindow(window->mdi_window);
- }
-}
-
-void DesktopInterface::addDialog(VirtualDialog* dialog) {
- if(oneWindow) {
- mdiArea->addSubWindow(dialog->mdi_window);
- }
-}
\ No newline at end of file
diff --git a/launcher/desktop/src/gamescopesettingswindow.cpp b/launcher/desktop/src/gamescopesettingswindow.cpp
deleted file mode 100644
index eb977bb..0000000
--- a/launcher/desktop/src/gamescopesettingswindow.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#include "gamescopesettingswindow.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "launchercore.h"
-
-GamescopeSettingsWindow::GamescopeSettingsWindow(DesktopInterface& interface, ProfileSettings& settings, LauncherCore& core, QWidget* parent)
- : VirtualDialog(interface, parent) {
- setWindowTitle("Gamescope Settings");
- setWindowModality(Qt::WindowModality::ApplicationModal);
-
- auto mainLayout = new QFormLayout();
- setLayout(mainLayout);
-
- auto fullscreenBox = new QCheckBox("Fullscreen");
- fullscreenBox->setChecked(settings.gamescope.fullscreen);
- connect(fullscreenBox, &QCheckBox::clicked, [&](bool checked) {
- settings.gamescope.fullscreen = checked;
-
- core.saveSettings();
- });
- mainLayout->addWidget(fullscreenBox);
-
- auto borderlessBox = new QCheckBox("Borderless");
- borderlessBox->setChecked(settings.gamescope.fullscreen);
- connect(borderlessBox, &QCheckBox::clicked, [&](bool checked) {
- settings.gamescope.borderless = checked;
-
- core.saveSettings();
- });
- mainLayout->addWidget(borderlessBox);
-
- auto widthBox = new QSpinBox();
- widthBox->setValue(settings.gamescope.width);
- widthBox->setSpecialValueText("Default");
- connect(widthBox, QOverload::of(&QSpinBox::valueChanged), [&](int value) {
- settings.gamescope.width = value;
-
- core.saveSettings();
- });
- mainLayout->addRow("Width", widthBox);
-
- auto heightBox = new QSpinBox();
- heightBox->setValue(settings.gamescope.height);
- heightBox->setSpecialValueText("Default");
- connect(heightBox, QOverload::of(&QSpinBox::valueChanged), [&](int value) {
- settings.gamescope.height = value;
-
- core.saveSettings();
- });
- mainLayout->addRow("Height", heightBox);
-
- auto refreshRateBox = new QSpinBox();
- refreshRateBox->setValue(settings.gamescope.refreshRate);
- refreshRateBox->setSpecialValueText("Default");
- connect(refreshRateBox, QOverload::of(&QSpinBox::valueChanged), [&](int value) {
- settings.gamescope.refreshRate = value;
-
- core.saveSettings();
- });
- mainLayout->addRow("Refresh Rate", refreshRateBox);
-}
\ No newline at end of file
diff --git a/launcher/desktop/src/launcherwindow.cpp b/launcher/desktop/src/launcherwindow.cpp
deleted file mode 100644
index b9b2948..0000000
--- a/launcher/desktop/src/launcherwindow.cpp
+++ /dev/null
@@ -1,556 +0,0 @@
-#include "launcherwindow.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "aboutwindow.h"
-#include "assetupdater.h"
-#include "bannerwidget.h"
-#include "encryptedarg.h"
-#include "gameinstaller.h"
-#include "headline.h"
-#include "sapphirelauncher.h"
-#include "settingswindow.h"
-#include "squarelauncher.h"
-#include "desktopinterface.h"
-
-LauncherWindow::LauncherWindow(DesktopInterface& interface, LauncherCore& core, QWidget* parent) : VirtualWindow(interface, parent), core(core), interface(interface) {
- setWindowTitle("Astra");
-
- connect(&core, &LauncherCore::settingsChanged, this, &LauncherWindow::reloadControls);
-
- QMenu* toolsMenu = menuBar()->addMenu("Tools");
-
- launchOfficial = toolsMenu->addAction("Open Official Launcher...");
- launchOfficial->setIcon(QIcon::fromTheme("application-x-executable"));
- connect(launchOfficial, &QAction::triggered, [=] {
- struct Argument {
- QString key, value;
- };
-
- QString executeArg("%1%2%3%4");
- QDateTime dateTime = QDateTime::currentDateTime();
- executeArg = executeArg.arg(dateTime.date().month() + 1, 2, 10, QLatin1Char('0'));
- executeArg = executeArg.arg(dateTime.date().day(), 2, 10, QLatin1Char('0'));
- executeArg = executeArg.arg(dateTime.time().hour(), 2, 10, QLatin1Char('0'));
- executeArg = executeArg.arg(dateTime.time().minute(), 2, 10, QLatin1Char('0'));
-
- QList arguments;
- arguments.push_back({"ExecuteArg", executeArg});
-
- // find user path
- QString userPath;
-
- // TODO: don't put this here
- QString searchDir;
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- searchDir = currentProfile().winePrefixPath + "/drive_c/users";
-#else
- searchDir = "C:/Users";
-#endif
-
- QDirIterator it(searchDir);
- while (it.hasNext()) {
- QString dir = it.next();
- QFileInfo fi(dir);
- QString fileName = fi.fileName();
-
- // FIXME: is there no easier way to filter out these in Qt?
- if (fi.fileName() != "Public" && fi.fileName() != "." && fi.fileName() != "..") {
- userPath = fileName;
- }
- }
-
- arguments.push_back(
- {"UserPath",
- QString(R"(C:\Users\%1\Documents\My Games\FINAL FANTASY XIV - A Realm Reborn)").arg(userPath)});
-
- const QString argFormat = " /%1 =%2";
-
- QString argJoined;
- for (auto& arg : arguments) {
- argJoined += argFormat.arg(arg.key, arg.value.replace(" ", " "));
- }
-
- QString finalArg = encryptGameArg(argJoined);
-
- auto launcherProcess = new QProcess();
- this->core.launchExecutable(currentProfile(),
- launcherProcess,
- {currentProfile().gamePath + "/boot/ffxivlauncher64.exe", finalArg},
- false,
- true);
- });
-
- launchSysInfo = toolsMenu->addAction("Open System Info...");
- launchSysInfo->setIcon(QIcon::fromTheme("application-x-executable"));
- connect(launchSysInfo, &QAction::triggered, [=] {
- auto sysinfoProcess = new QProcess();
- this->core.launchExecutable(currentProfile(),
- sysinfoProcess,
- {currentProfile().gamePath + "/boot/ffxivsysinfo64.exe"},
- false,
- false);
- });
-
- launchCfgBackup = toolsMenu->addAction("Open Config Backup...");
- launchCfgBackup->setIcon(QIcon::fromTheme("application-x-executable"));
- connect(launchCfgBackup, &QAction::triggered, [=] {
- auto configProcess = new QProcess();
- this->core.launchExecutable(currentProfile(),
- configProcess,
- {currentProfile().gamePath + "/boot/ffxivconfig64.exe"},
- false,
- false);
- });
-
- toolsMenu->addSeparator();
-
- openGameDir = toolsMenu->addAction("Open Game Directory...");
- openGameDir->setIcon(QIcon::fromTheme("document-open"));
- connect(openGameDir, &QAction::triggered, [=] {
- openPath(currentProfile().gamePath);
- });
-
- QMenu* gameMenu = menuBar()->addMenu("Game");
-
- auto installGameAction = gameMenu->addAction("Install game...");
- connect(installGameAction, &QAction::triggered, [this] {
- // TODO: lol duplication
- auto messageBox = new QMessageBox();
- messageBox->setIcon(QMessageBox::Icon::Question);
- messageBox->setText("Warning");
- messageBox->setInformativeText("FFXIV will be installed to your selected game directory.");
-
- QString detailedText = QString("Astra will install FFXIV for you at '%1'").arg(this->currentProfile().gamePath);
- detailedText.append(
- "\n\nIf you do not wish to install it to this location, please change your profile settings.");
-
- messageBox->setDetailedText(detailedText);
- messageBox->setWindowModality(Qt::WindowModal);
-
- auto installButton = messageBox->addButton("Install Game", QMessageBox::YesRole);
- connect(installButton, &QPushButton::clicked, [this, messageBox] {
- installGame(this->core, this->currentProfile(), [this, messageBox] {
- this->core.readGameVersion();
-
- messageBox->close();
- });
- });
-
- messageBox->addButton(QMessageBox::StandardButton::No);
- messageBox->setDefaultButton(installButton);
-
- messageBox->exec();
- });
-
- QMenu* fileMenu = menuBar()->addMenu("Settings");
-
- QAction* settingsAction = fileMenu->addAction("Configure Astra...");
- settingsAction->setIcon(QIcon::fromTheme("configure"));
- settingsAction->setMenuRole(QAction::MenuRole::PreferencesRole);
- connect(settingsAction, &QAction::triggered, [=, &interface] {
- auto window = new SettingsWindow(interface, 0, *this, this->core);
- connect(&this->core, &LauncherCore::settingsChanged, window, &SettingsWindow::reloadControls);
- window->show();
- });
-
- QAction* profilesAction = fileMenu->addAction("Configure Profiles...");
- profilesAction->setIcon(QIcon::fromTheme("configure"));
- profilesAction->setMenuRole(QAction::MenuRole::NoRole);
- connect(profilesAction, &QAction::triggered, [=, &interface] {
- auto window = new SettingsWindow(interface, 1, *this, this->core);
- connect(&this->core, &LauncherCore::settingsChanged, window, &SettingsWindow::reloadControls);
- window->show();
- });
-
-#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
- fileMenu->addSeparator();
-
- wineCfg = fileMenu->addAction("Configure Wine...");
- wineCfg->setMenuRole(QAction::MenuRole::NoRole);
- wineCfg->setIcon(QIcon::fromTheme("configure"));
- connect(wineCfg, &QAction::triggered, [=] {
- auto configProcess = new QProcess();
- this->core.launchExecutable(currentProfile(),
- configProcess,
- {"winecfg.exe"},
- false,
- false);
- });
-#endif
-
- QMenu* helpMenu = menuBar()->addMenu("Help");
- QAction* showAbout = helpMenu->addAction("About Astra");
- showAbout->setIcon(QIcon::fromTheme("help-about"));
- connect(showAbout, &QAction::triggered, [=, &interface] {
- auto window = new AboutWindow(interface);
- window->show();
- });
-
- QAction* showAboutQt = helpMenu->addAction("About Qt");
- showAboutQt->setIcon(QIcon::fromTheme("help-about"));
- connect(showAboutQt, &QAction::triggered, [=] {
- QMessageBox::aboutQt(nullptr);
- });
-
- layout = new QGridLayout();
-
- bannerScrollArea = new QScrollArea();
- bannerLayout = new QHBoxLayout();
- bannerLayout->setContentsMargins(0, 0, 0, 0);
- bannerLayout->setSpacing(0);
- bannerLayout->setSizeConstraint(QLayout::SizeConstraint::SetMinAndMaxSize);
- bannerParentWidget = new QWidget();
- bannerParentWidget->setFixedHeight(250);
- bannerScrollArea->setFixedWidth(640);
- bannerScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- bannerScrollArea->verticalScrollBar()->setEnabled(false);
- bannerScrollArea->horizontalScrollBar()->setEnabled(false);
-
- bannerScrollArea->setWidget(bannerParentWidget);
- bannerParentWidget->setLayout(bannerLayout);
-
- newsListView = new QTreeWidget();
- newsListView->setColumnCount(2);
- newsListView->setHeaderLabels({"Title", "Date"});
- connect(newsListView, &QTreeWidget::itemClicked, [](QTreeWidgetItem* item, int column) {
- auto url = item->data(0, Qt::UserRole).toUrl();
- qInfo() << "clicked" << url;
- QDesktopServices::openUrl(url);
- });
-
- loginLayout = new QFormLayout();
- layout->addLayout(loginLayout, 0, 1, 1, 1);
-
- profileSelect = new QComboBox();
- connect(profileSelect, static_cast(&QComboBox::currentIndexChanged), [=](int index) {
- reloadControls();
- });
-
- loginLayout->addRow("Profile", profileSelect);
-
- usernameEdit = new QLineEdit();
- loginLayout->addRow("Username", usernameEdit);
-
- rememberUsernameBox = new QCheckBox();
- connect(rememberUsernameBox, &QCheckBox::stateChanged, [=](int) {
- currentProfile().rememberUsername = rememberUsernameBox->isChecked();
- this->core.saveSettings();
- });
- loginLayout->addRow("Remember Username?", rememberUsernameBox);
-
- passwordEdit = new QLineEdit();
- passwordEdit->setEchoMode(QLineEdit::EchoMode::Password);
- loginLayout->addRow("Password", passwordEdit);
-
- rememberPasswordBox = new QCheckBox();
- connect(rememberPasswordBox, &QCheckBox::stateChanged, [=](int) {
- currentProfile().rememberPassword = rememberPasswordBox->isChecked();
- this->core.saveSettings();
- });
- loginLayout->addRow("Remember Password?", rememberPasswordBox);
-
- otpEdit = new QLineEdit();
- loginButton = new QPushButton("Login");
- registerButton = new QPushButton("Register");
-
- connect(otpEdit, &QLineEdit::returnPressed, [this] {
- if (loginButton->isEnabled())
- this->core.assetUpdater->update(currentProfile());
- });
-
- connect(passwordEdit, &QLineEdit::returnPressed, [this] {
- if (loginButton->isEnabled())
- this->core.assetUpdater->update(currentProfile());
- });
-
- auto emptyWidget = new QWidget();
- emptyWidget->setLayout(layout);
- setCentralWidget(emptyWidget);
-
- connect(core.assetUpdater, &AssetUpdater::finishedUpdating, [=] {
- auto& profile = currentProfile();
-
- auto info = new LoginInformation();
- info->settings = &profile;
- info->username = usernameEdit->text();
- info->password = passwordEdit->text();
- info->oneTimePassword = otpEdit->text();
-
- if (currentProfile().rememberUsername) {
- profile.setKeychainValue("username", usernameEdit->text());
- }
-
- if (currentProfile().rememberPassword) {
- profile.setKeychainValue("password", passwordEdit->text());
- }
-
- this->core.login(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& profile = currentProfile();
-
- LoginInformation info;
- info.settings = &profile;
- info.username = usernameEdit->text();
- info.password = passwordEdit->text();
- info.oneTimePassword = otpEdit->text();
-
- this->core.sapphireLauncher->registerAccount(currentProfile().lobbyURL, info);
- }
- });
-
- connect(&core, &LauncherCore::successfulLaunch, [&] {
- if (core.appSettings.closeWhenLaunched)
- hide();
- });
-
- connect(&core, &LauncherCore::gameClosed, [&] {
- if (core.appSettings.closeWhenLaunched)
- QCoreApplication::quit();
- });
-
- getHeadline(core, [&](Headline new_headline) {
- this->headline = std::move(new_headline);
- reloadNews();
- });
-
- reloadControls();
-}
-
-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) {
- usernameEdit->setText(currentProfile().getKeychainValue("username"));
- }
-
- rememberPasswordBox->setChecked(currentProfile().rememberPassword);
- if (currentProfile().rememberPassword) {
- passwordEdit->setText(currentProfile().getKeychainValue("password"));
- }
-
- bool canLogin = true;
- if (currentProfile().isSapphire) {
- if (currentProfile().lobbyURL.isEmpty()) {
- loginButton->setText("Login (Lobby URL is invalid)");
- canLogin = false;
- }
- }
-
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- if (!currentProfile().isWineInstalled() && !core.isSteam) {
- loginButton->setText("Login (Wine is not installed)");
- canLogin = false;
- }
-#endif
- if (!currentProfile().isGameInstalled()) {
- loginButton->setText("Login (Game is not installed)");
- canLogin = false;
- }
-
- if (canLogin)
- loginButton->setText("Login");
-
- launchOfficial->setEnabled(currentProfile().isGameInstalled());
- launchSysInfo->setEnabled(currentProfile().isGameInstalled());
- launchCfgBackup->setEnabled(currentProfile().isGameInstalled());
-
- // Steam Deck's Game session has no file manager, so no point in having it here...
- if(interface.isSteamDeck) {
- openGameDir->setDisabled(true);
- } else {
- openGameDir->setEnabled(currentProfile().isGameInstalled());
- }
-
-#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
- wineCfg->setEnabled(currentProfile().isWineInstalled());
-#endif
-
- layout->removeWidget(bannerScrollArea);
- bannerScrollArea->hide();
- layout->removeWidget(newsListView);
- newsListView->hide();
-
- auto field = loginLayout->labelForField(otpEdit);
- if (field != nullptr)
- field->deleteLater();
-
- loginLayout->takeRow(otpEdit);
- otpEdit->hide();
-
- if (currentProfile().useOneTimePassword && !currentProfile().isSapphire) {
- loginLayout->addRow("One-Time Password", otpEdit);
- otpEdit->show();
- }
-
- loginLayout->takeRow(loginButton);
- loginButton->setEnabled(canLogin);
- registerButton->setEnabled(canLogin);
- loginLayout->addRow(loginButton);
-
- loginLayout->takeRow(registerButton);
- registerButton->hide();
-
- if (currentProfile().isSapphire) {
- loginLayout->addRow(registerButton);
- registerButton->show();
- }
-
- reloadNews();
-
- currentlyReloadingControls = false;
-}
-
-void LauncherWindow::reloadNews() {
- if (core.appSettings.showBanners || core.appSettings.showNewsList) {
- for (auto widget : bannerWidgets) {
- bannerLayout->removeWidget(widget);
- }
-
- bannerWidgets.clear();
-
- int totalRow = 0;
- if (core.appSettings.showBanners) {
- bannerScrollArea->show();
- layout->addWidget(bannerScrollArea, totalRow++, 0);
- }
-
- if (core.appSettings.showNewsList) {
- newsListView->show();
- layout->addWidget(newsListView, totalRow++, 0);
- }
-
- newsListView->clear();
-
- if (!headline.banner.empty()) {
- if (core.appSettings.showBanners) {
- for (const auto& banner : headline.banner) {
- auto request = QNetworkRequest(banner.bannerImage);
- core.buildRequest(currentProfile(), request);
-
- auto reply = core.mgr->get(request);
- connect(reply, &QNetworkReply::finished, [=] {
- auto bannerImageView = new BannerWidget();
- bannerImageView->setUrl(banner.link);
-
- QPixmap pixmap;
- pixmap.loadFromData(reply->readAll());
- bannerImageView->setPixmap(pixmap);
-
- bannerLayout->addWidget(bannerImageView);
- bannerWidgets.push_back(bannerImageView);
- });
- }
-
- if (bannerTimer == nullptr) {
- bannerTimer = new QTimer();
- connect(bannerTimer, &QTimer::timeout, this, [=] {
- if (currentBanner >= headline.banner.size())
- currentBanner = 0;
-
- bannerScrollArea->ensureVisible(640 * (currentBanner + 1), 0, 0, 0);
-
- currentBanner++;
- });
- bannerTimer->start(5000);
- }
- } else {
- if (bannerTimer != nullptr) {
- bannerTimer->stop();
- bannerTimer->deleteLater();
- bannerTimer = nullptr;
- }
- }
-
- if (core.appSettings.showNewsList) {
- auto newsItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList("News"));
- for (const auto& news : headline.news) {
- auto item = new QTreeWidgetItem();
- item->setText(0, news.title);
- item->setText(1, QLocale().toString(news.date, QLocale::ShortFormat));
- item->setData(0, Qt::UserRole, news.url);
-
- newsItem->addChild(item);
- }
-
- auto pinnedItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList("Pinned"));
- for (const auto& pinned : headline.pinned) {
- auto item = new QTreeWidgetItem();
- item->setText(0, pinned.title);
- item->setText(1, QLocale().toString(pinned.date, QLocale::ShortFormat));
- item->setData(0, Qt::UserRole, pinned.url);
-
- pinnedItem->addChild(item);
- }
-
- auto topicsItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList("Topics"));
- for (const auto& news : headline.topics) {
- auto item = new QTreeWidgetItem();
- item->setText(0, news.title);
- item->setText(1, QLocale().toString(news.date, QLocale::ShortFormat));
- item->setData(0, Qt::UserRole, news.url);
-
- topicsItem->addChild(item);
- }
-
- newsListView->insertTopLevelItems(0, QList({newsItem, pinnedItem, topicsItem}));
-
- for (int i = 0; i < 3; i++) {
- newsListView->expandItem(newsListView->topLevelItem(i));
- newsListView->resizeColumnToContents(i);
- }
- }
- }
- }
-}
-
-void LauncherWindow::openPath(const QString& path) {
-#if defined(Q_OS_WIN)
- // for some reason, windows requires special treatment (what else is new?)
- const QFileInfo fileInfo(path);
-
- QProcess::startDetached("explorer.exe", QStringList(QDir::toNativeSeparators(fileInfo.canonicalFilePath())));
-#else
- QDesktopServices::openUrl("file://" + path);
-#endif
-}
diff --git a/launcher/desktop/src/settingswindow.cpp b/launcher/desktop/src/settingswindow.cpp
deleted file mode 100644
index b2addba..0000000
--- a/launcher/desktop/src/settingswindow.cpp
+++ /dev/null
@@ -1,694 +0,0 @@
-#include "settingswindow.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "gamescopesettingswindow.h"
-#include "launchercore.h"
-#include "launcherwindow.h"
-
-SettingsWindow::SettingsWindow(DesktopInterface& interface, int defaultTab, LauncherWindow& window, LauncherCore& core, QWidget* parent)
- : core(core), window(window), interface(interface), VirtualDialog(interface, parent) {
- setWindowTitle("Settings");
- setWindowModality(Qt::WindowModality::ApplicationModal);
-
- auto mainLayout = new QVBoxLayout();
- setLayout(mainLayout);
-
- auto tabWidget = new QTabWidget();
- mainLayout->addWidget(tabWidget);
-
- // general tab
- {
- auto generalTabWidget = new QWidget();
- tabWidget->addTab(generalTabWidget, "General");
-
- auto layout = new QFormLayout();
- generalTabWidget->setLayout(layout);
-
- closeWhenLaunched = new QCheckBox("Close Astra when game is launched");
- connect(closeWhenLaunched, &QCheckBox::stateChanged, [&](int state) {
- core.appSettings.closeWhenLaunched = state;
-
- core.saveSettings();
- });
- layout->addWidget(closeWhenLaunched);
-
- showBanner = new QCheckBox("Show news banners");
- connect(showBanner, &QCheckBox::stateChanged, [&](int state) {
- core.appSettings.showBanners = state;
-
- core.saveSettings();
- window.reloadControls();
- });
- layout->addWidget(showBanner);
-
- showNewsList = new QCheckBox("Show news list");
- connect(showNewsList, &QCheckBox::stateChanged, [&](int state) {
- core.appSettings.showNewsList = state;
-
- core.saveSettings();
- window.reloadControls();
- });
- layout->addWidget(showNewsList);
- }
-
- // profile tab
- {
- auto profileTabWidget = new QWidget();
- tabWidget->addTab(profileTabWidget, "Profiles");
-
- auto profileLayout = new QGridLayout();
- profileTabWidget->setLayout(profileLayout);
-
- auto profileTabs = new QTabWidget();
- profileLayout->addWidget(profileTabs, 1, 1, 3, 3);
-
- profileWidget = new QListWidget();
- profileWidget->addItem("INVALID *DEBUG*");
- profileWidget->setCurrentRow(0);
-
- connect(profileWidget, &QListWidget::currentRowChanged, this, &SettingsWindow::reloadControls);
-
- profileLayout->addWidget(profileWidget, 0, 0, 3, 1);
-
- auto addProfileButton = new QPushButton("Add Profile");
- connect(addProfileButton, &QPushButton::pressed, [=] {
- profileWidget->setCurrentRow(this->core.addProfile());
-
- this->core.saveSettings();
- });
- profileLayout->addWidget(addProfileButton, 3, 0);
-
- deleteAccountButton = new QPushButton("Delete Profile");
- connect(deleteAccountButton, &QPushButton::pressed, [=] {
- profileWidget->setCurrentRow(this->core.deleteProfile(getCurrentProfile().name));
-
- this->core.saveSettings();
- });
- profileLayout->addWidget(deleteAccountButton, 0, 2);
-
- nameEdit = new QLineEdit();
- connect(nameEdit, &QLineEdit::editingFinished, [=] {
- getCurrentProfile().name = nameEdit->text();
-
- reloadControls();
- this->core.saveSettings();
- });
- profileLayout->addWidget(nameEdit, 0, 1);
-
- // game options
- {
- auto gameTabWidget = new QWidget();
- profileTabs->addTab(gameTabWidget, "Game");
-
- auto gameBoxLayout = new QFormLayout();
- gameTabWidget->setLayout(gameBoxLayout);
-
- setupGameTab(*gameBoxLayout);
- }
-
- // login options
- {
- auto loginTabWidget = new QWidget();
- profileTabs->addTab(loginTabWidget, "Login");
-
- auto loginBoxLayout = new QFormLayout();
- loginTabWidget->setLayout(loginBoxLayout);
-
- setupLoginTab(*loginBoxLayout);
- }
-
-#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
- // wine options
- {
- auto wineTabWidget = new QWidget();
- profileTabs->addTab(wineTabWidget, "Wine");
-
- auto wineBoxLayout = new QFormLayout();
- wineTabWidget->setLayout(wineBoxLayout);
-
- setupWineTab(*wineBoxLayout);
- }
-#endif
-
- // dalamud options
- {
- auto dalamudTabWidget = new QWidget();
- profileTabs->addTab(dalamudTabWidget, "Dalamud");
-
- auto dalamudBoxLayout = new QFormLayout();
- dalamudTabWidget->setLayout(dalamudBoxLayout);
-
- setupDalamudTab(*dalamudBoxLayout);
- }
- }
-
- {
- auto accountsTabWidget = new QWidget();
- tabWidget->addTab(accountsTabWidget, "Accounts");
-
- auto accountsLayout = new QGridLayout();
- accountsTabWidget->setLayout(accountsLayout);
-
- setupAccountsTab(*accountsLayout);
- }
-
- tabWidget->setCurrentIndex(defaultTab);
-
- reloadControls();
-}
-
-void SettingsWindow::reloadControls() {
- if (currentlyReloadingControls)
- return;
-
- currentlyReloadingControls = true;
-
- auto oldRow = profileWidget->currentRow();
-
- profileWidget->clear();
-
- for (const auto& profile : core.profileList()) {
- profileWidget->addItem(profile);
- }
- profileWidget->setCurrentRow(oldRow);
-
- closeWhenLaunched->setChecked(core.appSettings.closeWhenLaunched);
- showBanner->setChecked(core.appSettings.showBanners);
- showNewsList->setChecked(core.appSettings.showNewsList);
-
- // deleting the main profile is unsupported behavior
- deleteAccountButton->setEnabled(profileWidget->currentRow() != 0);
-
- ProfileSettings& profile = core.getProfile(profileWidget->currentRow());
- nameEdit->setText(profile.name);
-
- // game
- directXCombo->setCurrentIndex(profile.useDX9 ? 1 : 0);
- currentGameDirectory->setText(profile.gamePath);
-
- if (!profile.isGameInstalled()) {
- expansionVersionLabel->setText("No game installed.");
- } else {
- QString expacString;
-
- expacString += "Boot";
- expacString += QString(" (%1)\n").arg(profile.bootVersion);
-
- for (int i = 0; i < profile.repositories.repositories_count; i++) {
- QString expansionName = "Unknown Expansion";
- if (i < core.expansionNames.size()) {
- expansionName = core.expansionNames[i];
- }
-
- expacString += expansionName;
- expacString += QString(" (%1)\n").arg(profile.repositories.repositories[i].version);
- }
-
- expansionVersionLabel->setText(expacString);
- }
-
- // wine
-#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
- if(!core.isSteam) {
- if (!profile.isWineInstalled()) {
- wineVersionLabel->setText("Wine is not installed.");
- } else {
- wineVersionLabel->setText(profile.wineVersion);
- }
-
- wineTypeCombo->setCurrentIndex((int)profile.wineType);
- selectWineButton->setEnabled(profile.wineType == WineType::Custom);
- winePathLabel->setText(profile.winePath);
- winePrefixDirectory->setText(profile.winePrefixPath);
- }
-#endif
-
-#if defined(Q_OS_LINUX)
- useEsync->setChecked(profile.useEsync);
- useGamescope->setChecked(profile.useGamescope);
- useGamemode->setChecked(profile.useGamemode);
-
-#ifndef ENABLE_GAMEMODE
- useGamemode->setEnabled(false);
-#endif
-
- useGamemode->setEnabled(core.gamemodeAvailable);
- useGamescope->setEnabled(core.gamescopeAvailable);
-
- configureGamescopeButton->setEnabled(profile.useGamescope);
-#endif
-
-#ifdef ENABLE_WATCHDOG
- enableWatchdog->setChecked(profile.enableWatchdog);
-#endif
-
- // login
- encryptArgumentsBox->setChecked(profile.encryptArguments);
- serverType->setCurrentIndex(profile.isSapphire ? 1 : 0);
- lobbyServerURL->setEnabled(profile.isSapphire);
- if (profile.isSapphire) {
- lobbyServerURL->setText(profile.lobbyURL);
- lobbyServerURL->setPlaceholderText("Required...");
- } else {
- lobbyServerURL->setText("neolobby0X.ffxiv.com");
- }
- rememberUsernameBox->setChecked(profile.rememberUsername);
- rememberPasswordBox->setChecked(profile.rememberPassword);
- rememberOTPSecretBox->setChecked(profile.rememberOTPSecret);
- rememberOTPSecretBox->setEnabled(profile.useOneTimePassword);
- otpSecretButton->setEnabled(profile.rememberOTPSecret);
- useOneTimePassword->setChecked(profile.useOneTimePassword);
- useOneTimePassword->setEnabled(!profile.isSapphire);
- if (!useOneTimePassword->isEnabled()) {
- useOneTimePassword->setToolTip("OTP is not supported by Sapphire servers.");
- } else {
- useOneTimePassword->setToolTip("");
- }
- autoLoginBox->setChecked(profile.autoLogin);
-
- gameLicenseBox->setCurrentIndex((int)profile.license);
- gameLicenseBox->setEnabled(!profile.isSapphire);
- if (!gameLicenseBox->isEnabled()) {
- gameLicenseBox->setToolTip("Game licenses only matter when logging into the official Square Enix servers.");
- } else {
- gameLicenseBox->setToolTip("");
- }
-
- freeTrialBox->setChecked(profile.isFreeTrial);
-
- // dalamud
- enableDalamudBox->setChecked(profile.dalamud.enabled);
- if (core.dalamudVersion.isEmpty()) {
- dalamudVersionLabel->setText("Dalamud is not installed.");
- } else {
- dalamudVersionLabel->setText(core.dalamudVersion);
- }
-
- if (core.dalamudAssetVersion == -1) {
- dalamudAssetVersionLabel->setText("Dalamud assets are not installed.");
- } else {
- dalamudAssetVersionLabel->setText(QString::number(core.dalamudAssetVersion));
- }
-
- dalamudOptOutBox->setChecked(profile.dalamud.optOutOfMbCollection);
- dalamudChannel->setCurrentIndex((int)profile.dalamud.channel);
-
- window.reloadControls();
-
- currentlyReloadingControls = false;
-}
-
-ProfileSettings& SettingsWindow::getCurrentProfile() {
- return this->core.getProfile(profileWidget->currentRow());
-}
-
-void SettingsWindow::setupGameTab(QFormLayout& layout) {
- directXCombo = new QComboBox();
- directXCombo->addItem("DirectX 11");
- directXCombo->addItem("DirectX 9");
- layout.addRow("DirectX Version", directXCombo);
-
- connect(directXCombo, static_cast(&QComboBox::currentIndexChanged), [=](int index) {
- getCurrentProfile().useDX9 = directXCombo->currentIndex() == 1;
- this->core.saveSettings();
- });
-
- currentGameDirectory = new QLabel();
- currentGameDirectory->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Game Directory", currentGameDirectory);
-
- auto gameDirButtonLayout = new QHBoxLayout();
- auto gameDirButtonContainer = new QWidget();
- gameDirButtonContainer->setLayout(gameDirButtonLayout);
- layout.addWidget(gameDirButtonContainer);
-
- auto selectDirectoryButton = new QPushButton("Select Game Directory");
- connect(selectDirectoryButton, &QPushButton::pressed, [this] {
- getCurrentProfile().gamePath = QFileDialog::getExistingDirectory(nullptr, "Open Game Directory");
-
- this->reloadControls();
- this->core.saveSettings();
-
- this->core.readGameVersion();
- });
- gameDirButtonLayout->addWidget(selectDirectoryButton);
-
- gameDirectoryButton = new QPushButton("Open Game Directory");
- connect(gameDirectoryButton, &QPushButton::pressed, [this] {
- window.openPath(getCurrentProfile().gamePath);
- });
- gameDirButtonLayout->addWidget(gameDirectoryButton);
-
-#ifdef ENABLE_WATCHDOG
- enableWatchdog = new QCheckBox("Enable Watchdog (X11 only)");
- layout.addWidget(enableWatchdog);
-
- connect(enableWatchdog, &QCheckBox::stateChanged, [this](int state) {
- getCurrentProfile().enableWatchdog = state;
-
- this->core.saveSettings();
- });
-#endif
-
- gameDirectoryButton->setEnabled(getCurrentProfile().isGameInstalled());
-
- expansionVersionLabel = new QLabel();
- expansionVersionLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Game Version", expansionVersionLabel);
-}
-
-void SettingsWindow::setupLoginTab(QFormLayout& layout) {
- encryptArgumentsBox = new QCheckBox();
- connect(encryptArgumentsBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().encryptArguments = encryptArgumentsBox->isChecked();
-
- this->core.saveSettings();
- });
- layout.addRow("Encrypt Game Arguments", encryptArgumentsBox);
-
- serverType = new QComboBox();
- serverType->insertItem(0, "Square Enix");
- serverType->insertItem(1, "Sapphire");
-
- connect(serverType, static_cast(&QComboBox::currentIndexChanged), [=](int index) {
- getCurrentProfile().isSapphire = index == 1;
-
- reloadControls();
- this->core.saveSettings();
- });
-
- layout.addRow("Server Lobby", serverType);
-
- lobbyServerURL = new QLineEdit();
- connect(lobbyServerURL, &QLineEdit::editingFinished, [=] {
- getCurrentProfile().lobbyURL = lobbyServerURL->text();
- this->core.saveSettings();
- });
- layout.addRow("Lobby URL", lobbyServerURL);
-
- gameLicenseBox = new QComboBox();
- gameLicenseBox->insertItem(0, "Windows (Standalone)");
- gameLicenseBox->insertItem(1, "Windows (Steam)");
- gameLicenseBox->insertItem(2, "macOS");
-
- connect(gameLicenseBox, static_cast(&QComboBox::currentIndexChanged), [=](int index) {
- getCurrentProfile().license = (GameLicense)index;
-
- this->core.saveSettings();
- });
-
- layout.addRow("Game License", gameLicenseBox);
-
- freeTrialBox = new QCheckBox();
- connect(freeTrialBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().isFreeTrial = freeTrialBox->isChecked();
-
- this->core.saveSettings();
- });
- layout.addRow("Is Free Trial", freeTrialBox);
-
- rememberUsernameBox = new QCheckBox();
- connect(rememberUsernameBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().rememberUsername = rememberUsernameBox->isChecked();
-
- this->core.saveSettings();
- });
- rememberUsernameBox->setToolTip("Relatively harmless option, can save your password for later for convince.");
- layout.addRow("Remember Username", rememberUsernameBox);
-
- rememberPasswordBox = new QCheckBox();
- connect(rememberPasswordBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().rememberPassword = rememberPasswordBox->isChecked();
-
- this->core.saveSettings();
- });
- rememberPasswordBox->setToolTip("You should only save your password when using OTP and you're fairly confident your system can keep it's keychain secure.");
- layout.addRow("Remember Password", rememberPasswordBox);
-
- rememberOTPSecretBox = new QCheckBox();
- connect(rememberOTPSecretBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().rememberOTPSecret = rememberOTPSecretBox->isChecked();
-
- this->core.saveSettings();
- this->reloadControls();
- });
- rememberOTPSecretBox->setToolTip("DANGEROUS! This should only be set if you're confident that your system keychain can securely store this. This trades convenience over the security an OTP can guarantee, so please be aware of that.");
- layout.addRow("Remember OTP Secret", rememberOTPSecretBox);
-
- otpSecretButton = new QPushButton("Enter OTP Secret");
- connect(otpSecretButton, &QPushButton::pressed, [=] {
- auto otpSecret = QInputDialog::getText(nullptr, "OTP Input", "Enter your OTP Secret:");
-
- getCurrentProfile().setKeychainValue("otpsecret", otpSecret);
- });
- otpSecretButton->setToolTip("Enter your OTP secret from Square Enix here. You cannot easily retrieve this if you forget it.");
- layout.addRow(otpSecretButton);
-
- useOneTimePassword = new QCheckBox();
- connect(useOneTimePassword, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().useOneTimePassword = useOneTimePassword->isChecked();
-
- this->core.saveSettings();
- this->window.reloadControls();
- this->reloadControls();
- });
- layout.addRow("Use One-Time Password", useOneTimePassword);
-
- autoLoginBox = new QCheckBox();
- connect(autoLoginBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().autoLogin = autoLoginBox->isChecked();
-
- this->core.saveSettings();
- this->window.reloadControls();
- });
- layout.addRow("Auto-Login", autoLoginBox);
-}
-
-void SettingsWindow::setupWineTab(QFormLayout& layout) {
-#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
- if(!core.isSteam) {
- winePathLabel = new QLabel();
- winePathLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Wine Executable", winePathLabel);
-
- wineTypeCombo = new QComboBox();
-
- #if defined(Q_OS_MAC)
- wineTypeCombo->insertItem(2, "FFXIV for Mac (Official)");
- wineTypeCombo->insertItem(3, "XIV on Mac");
- #endif
-
- wineTypeCombo->insertItem(0, "System Wine");
-
- // custom wine selection is broken under flatpak
- #ifndef FLATPAK
- wineTypeCombo->insertItem(1, "Custom Wine");
- #endif
-
- layout.addWidget(wineTypeCombo);
-
- selectWineButton = new QPushButton("Select Wine Executable");
-
- #ifndef FLATPAK
- layout.addWidget(selectWineButton);
- #endif
-
- connect(
- wineTypeCombo, static_cast(&QComboBox::currentIndexChanged), [this](int index) {
- getCurrentProfile().wineType = (WineType)index;
-
- this->core.readWineInfo(getCurrentProfile());
- this->core.saveSettings();
- this->reloadControls();
- });
-
- connect(selectWineButton, &QPushButton::pressed, [this] {
- getCurrentProfile().winePath = QFileDialog::getOpenFileName(nullptr, "Open Wine Executable");
-
- this->core.saveSettings();
- this->reloadControls();
- });
-
- // wine version is reported incorrectly under flatpak too
- wineVersionLabel = new QLabel();
- #ifndef FLATPAK
- wineVersionLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Wine Version", wineVersionLabel);
- #endif
-
- winePrefixDirectory = new QLabel();
- winePrefixDirectory->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Wine Prefix", winePrefixDirectory);
-
- auto winePrefixButtonLayout = new QHBoxLayout();
- auto winePrefixButtonContainer = new QWidget();
- winePrefixButtonContainer->setLayout(winePrefixButtonLayout);
- layout.addWidget(winePrefixButtonContainer);
-
- auto selectPrefixButton = new QPushButton("Select Wine Prefix");
- connect(selectPrefixButton, &QPushButton::pressed, [this] {
- getCurrentProfile().winePrefixPath = QFileDialog::getExistingDirectory(nullptr, "Open Wine Prefix");
-
- this->core.saveSettings();
- this->reloadControls();
- });
- winePrefixButtonLayout->addWidget(selectPrefixButton);
-
- auto openPrefixButton = new QPushButton("Open Wine Prefix");
- connect(openPrefixButton, &QPushButton::pressed, [this] {
- window.openPath(getCurrentProfile().winePrefixPath);
- });
- winePrefixButtonLayout->addWidget(openPrefixButton);
- } else {
- auto label = new QLabel("You are launching Astra via Steam. Proton is used automatically and can not be configured.");
- layout.addWidget(label);
- }
-
- auto enableDXVKhud = new QCheckBox("Enable DXVK HUD");
- layout.addRow("Wine Tweaks", enableDXVKhud);
-
- connect(enableDXVKhud, &QCheckBox::stateChanged, [this](int state) {
- getCurrentProfile().enableDXVKhud = state;
- this->core.settings.setValue("enableDXVKhud", static_cast(state));
- });
-#endif
-
-#if defined(Q_OS_LINUX)
- useEsync = new QCheckBox("Use Better Sync Primitives (Esync, Fsync, and Futex2)");
- layout.addWidget(useEsync);
-
- useEsync->setToolTip(
- "This may improve game performance, but requires a Wine and kernel with the patches included.");
-
- connect(useEsync, &QCheckBox::stateChanged, [this](int state) {
- getCurrentProfile().useEsync = state;
-
- this->core.saveSettings();
- });
-
- useGamescope = new QCheckBox("Use Gamescope");
- layout.addWidget(useGamescope);
-
- useGamescope->setToolTip(
- "Use the micro-compositor compositor that uses Wayland and XWayland to create a nested session.\nIf you "
- "primarily use fullscreen mode, this may improve input handling especially on Wayland.");
-
- auto gamescopeButtonLayout = new QHBoxLayout();
- auto gamescopeButtonContainer = new QWidget();
- gamescopeButtonContainer->setLayout(gamescopeButtonLayout);
- layout.addWidget(gamescopeButtonContainer);
-
- configureGamescopeButton = new QPushButton("Configure...");
- connect(configureGamescopeButton, &QPushButton::pressed, [&] {
- auto gamescopeSettingsWindow = new GamescopeSettingsWindow(interface, getCurrentProfile(), this->core, this->getRootWidget());
- gamescopeSettingsWindow->show();
- });
- gamescopeButtonLayout->addWidget(configureGamescopeButton);
-
- connect(useGamescope, &QCheckBox::stateChanged, [this](int state) {
- getCurrentProfile().useGamescope = state;
-
- this->core.saveSettings();
- this->reloadControls();
- });
-
- useGamemode = new QCheckBox("Use GameMode");
- layout.addWidget(useGamemode);
-
- useGamemode->setToolTip("A special game performance enhancer, which automatically tunes your CPU scheduler among "
- "other things. This may improve game performance.");
-
- connect(useGamemode, &QCheckBox::stateChanged, [this](int state) {
- getCurrentProfile().useGamemode = state;
-
- this->core.saveSettings();
- });
-#endif
-}
-
-void SettingsWindow::setupDalamudTab(QFormLayout& layout) {
- enableDalamudBox = new QCheckBox();
- connect(enableDalamudBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().dalamud.enabled = enableDalamudBox->isChecked();
-
- this->core.saveSettings();
- });
- layout.addRow("Enable Dalamud Plugins", enableDalamudBox);
-
- dalamudOptOutBox = new QCheckBox();
- connect(dalamudOptOutBox, &QCheckBox::stateChanged, [=](int) {
- getCurrentProfile().dalamud.optOutOfMbCollection = dalamudOptOutBox->isChecked();
-
- this->core.saveSettings();
- });
- layout.addRow("Opt Out of Automatic Marketboard Collection", dalamudOptOutBox);
-
- dalamudChannel = new QComboBox();
- dalamudChannel->insertItem(0, "Stable");
- dalamudChannel->insertItem(1, "Staging");
- dalamudChannel->insertItem(2, ".NET 5");
-
- connect(dalamudChannel, static_cast(&QComboBox::currentIndexChanged), [=](int index) {
- getCurrentProfile().dalamud.channel = (DalamudChannel)index;
-
- this->core.saveSettings();
- });
-
- layout.addRow("Dalamud Update Channel", dalamudChannel);
-
- dalamudVersionLabel = new QLabel();
- dalamudVersionLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Dalamud Version", dalamudVersionLabel);
-
- dalamudAssetVersionLabel = new QLabel();
- dalamudAssetVersionLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse);
- layout.addRow("Dalamud Asset Version", dalamudAssetVersionLabel);
-}
-
-void SettingsWindow::setupAccountsTab(QGridLayout& layout) {
- auto profileTabs = new QTabWidget();
- layout.addWidget(profileTabs, 1, 1, 3, 3);
-
- accountWidget = new QListWidget();
- accountWidget->addItem("INVALID *DEBUG*");
- accountWidget->setCurrentRow(0);
-
- connect(accountWidget, &QListWidget::currentRowChanged, this, &SettingsWindow::reloadControls);
-
- layout.addWidget(accountWidget, 0, 0, 3, 1);
-
- auto addAccountButton = new QPushButton("Add Account");
- connect(addAccountButton, &QPushButton::pressed, [=] {
- accountWidget->setCurrentRow(this->core.addProfile());
-
- this->core.saveSettings();
- });
- layout.addWidget(addAccountButton, 3, 0);
-
- deleteAccountButton = new QPushButton("Remove Account");
- connect(deleteAccountButton, &QPushButton::pressed, [=] {
- accountWidget->setCurrentRow(this->core.deleteProfile(getCurrentProfile().name));
-
- this->core.saveSettings();
- });
- layout.addWidget(deleteAccountButton, 0, 2);
-
- nameEdit = new QLineEdit();
- connect(nameEdit, &QLineEdit::editingFinished, [=] {
- //getCurrentProfile().name = nameEdit->text();
-
- reloadControls();
- this->core.saveSettings();
- });
- layout.addWidget(nameEdit, 0, 1);
-}
\ No newline at end of file
diff --git a/launcher/desktop/src/virtualdialog.cpp b/launcher/desktop/src/virtualdialog.cpp
deleted file mode 100644
index 3861a38..0000000
--- a/launcher/desktop/src/virtualdialog.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-#include "virtualdialog.h"
-
-#include
-
-#include "desktopinterface.h"
-
-VirtualDialog::VirtualDialog(DesktopInterface& interface, QWidget* widget) : interface(interface), QObject(widget) {
- if (interface.oneWindow) {
- mdi_window = new QMdiSubWindow();
- mdi_window->setAttribute(Qt::WA_DeleteOnClose);
- } else {
- normal_dialog = new QDialog();
- }
-
- interface.addDialog(this);
-}
-
-void VirtualDialog::setWindowTitle(const QString& title) {
- if (interface.oneWindow) {
- mdi_window->setWindowTitle(title);
- } else {
- normal_dialog->setWindowTitle(title);
- }
-}
-
-void VirtualDialog::show() {
- if (interface.oneWindow) {
- mdi_window->show();
- } else {
- normal_dialog->show();
- }
-}
-
-void VirtualDialog::hide() {
- if(interface.oneWindow) {
- mdi_window->hide();
- } else {
- normal_dialog->hide();
- }
-}
-
-void VirtualDialog::close() {
- if(interface.oneWindow) {
- mdi_window->close();
- } else {
- normal_dialog->close();
- }
-}
-
-void VirtualDialog::setWindowModality(Qt::WindowModality modality) {
- if(interface.oneWindow) {
- mdi_window->setWindowModality(modality);
- } else {
- normal_dialog->setWindowModality(modality);
- }
-}
-
-void VirtualDialog::setLayout(QLayout* layout) {
- if(interface.oneWindow) {
- auto emptyWidget = new QWidget();
- emptyWidget->setLayout(layout);
-
- mdi_window->layout()->addWidget(emptyWidget);
- } else {
- normal_dialog->setLayout(layout);
- }
-}
-
-QWidget* VirtualDialog::getRootWidget() {
- if(interface.oneWindow) {
- return mdi_window;
- } else {
- return normal_dialog;
- }
-}
diff --git a/launcher/desktop/src/virtualwindow.cpp b/launcher/desktop/src/virtualwindow.cpp
deleted file mode 100644
index 38cad98..0000000
--- a/launcher/desktop/src/virtualwindow.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-#include "virtualwindow.h"
-
-#include
-#include
-
-#include "desktopinterface.h"
-
-VirtualWindow::VirtualWindow(DesktopInterface& interface, QWidget* widget) : interface(interface), QObject(widget) {
- if (interface.oneWindow) {
- mdi_window = new QMdiSubWindow();
- mdi_window->setAttribute(Qt::WA_DeleteOnClose);
- } else {
- normal_window = new QMainWindow();
- }
-
- interface.addWindow(this);
-}
-
-void VirtualWindow::setWindowTitle(const QString& title) {
- if (interface.oneWindow) {
- mdi_window->setWindowTitle(title);
- } else {
- normal_window->setWindowTitle(title);
- }
-}
-
-void VirtualWindow::show() {
- if(interface.oneWindow) {
- mdi_window->show();
- } else {
- normal_window->show();
- }
-}
-
-void VirtualWindow::setCentralWidget(QWidget* widget) {
- if(interface.oneWindow) {
- mdi_window->layout()->addWidget(widget);
- } else {
- normal_window->setCentralWidget(widget);
- }
-}
-
-void VirtualWindow::hide() {
- if(interface.oneWindow) {
- mdi_window->hide();
- } else {
- normal_window->hide();
- }
-}
-
-QMenuBar* VirtualWindow::menuBar() {
- if(interface.oneWindow) {
- if(mdi_window->layout()->menuBar() == nullptr) {
- mdi_window->layout()->setMenuBar(new QMenuBar());
- }
-
- return dynamic_cast(mdi_window->layout()->menuBar());
- } else {
- return normal_window->menuBar();
- }
-}
-
-void VirtualWindow::showMaximized() {
- if(interface.oneWindow) {
- mdi_window->showMaximized();
- } else {
- normal_window->showMaximized();
- }
-}
-
-QWidget* VirtualWindow::getRootWidget() {
- if(interface.oneWindow) {
- return mdi_window;
- } else {
- return normal_window;
- }
-}
diff --git a/launcher/include/account.h b/launcher/include/account.h
new file mode 100644
index 0000000..57c8d07
--- /dev/null
+++ b/launcher/include/account.h
@@ -0,0 +1,95 @@
+#pragma once
+
+#include
+
+#include "accountconfig.h"
+
+class LauncherCore;
+
+class Account : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+ Q_PROPERTY(QString lodestoneId READ lodestoneId WRITE setLodestoneId NOTIFY lodestoneIdChanged)
+ Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
+ Q_PROPERTY(bool isSapphire READ isSapphire WRITE setIsSapphire NOTIFY isSapphireChanged)
+ Q_PROPERTY(QString lobbyUrl READ lobbyUrl WRITE setLobbyUrl NOTIFY lobbyUrlChanged)
+ Q_PROPERTY(bool rememberPassword READ rememberPassword WRITE setRememberPassword NOTIFY rememberPasswordChanged)
+ Q_PROPERTY(bool rememberOTP READ rememberOTP WRITE setRememberOTP NOTIFY rememberOTPChanged)
+ Q_PROPERTY(bool useOTP READ useOTP WRITE setUseOTP NOTIFY useOTPChanged)
+ Q_PROPERTY(GameLicense license READ license WRITE setLicense NOTIFY licenseChanged)
+ Q_PROPERTY(bool isFreeTrial READ isFreeTrial WRITE setIsFreeTrial NOTIFY isFreeTrialChanged)
+
+public:
+ explicit Account(LauncherCore &launcher, const QString &key, QObject *parent = nullptr);
+
+ enum class GameLicense { WindowsStandalone, WindowsSteam, macOS };
+ Q_ENUM(GameLicense)
+
+ QString uuid() const;
+
+ QString name() const;
+ void setName(const QString &name);
+
+ QString lodestoneId() const;
+ void setLodestoneId(const QString &id);
+
+ QString avatarUrl() const;
+
+ bool isSapphire() const;
+ void setIsSapphire(bool value);
+
+ QString lobbyUrl() const;
+ void setLobbyUrl(const QString &url);
+
+ bool rememberPassword() const;
+ void setRememberPassword(bool value);
+
+ bool rememberOTP() const;
+ void setRememberOTP(bool value);
+
+ bool useOTP() const;
+ void setUseOTP(bool value);
+
+ GameLicense license() const;
+ void setLicense(GameLicense license);
+
+ bool isFreeTrial() const;
+ void setIsFreeTrial(bool value);
+
+ Q_INVOKABLE QString getPassword() const;
+ void setPassword(const QString &password);
+
+ Q_INVOKABLE QString getOTP() const;
+
+Q_SIGNALS:
+ void nameChanged();
+ void lodestoneIdChanged();
+ void avatarUrlChanged();
+ void isSapphireChanged();
+ void lobbyUrlChanged();
+ void rememberPasswordChanged();
+ void rememberOTPChanged();
+ void useOTPChanged();
+ void licenseChanged();
+ void isFreeTrialChanged();
+
+private:
+ void fetchAvatar();
+
+ /*
+ * Sets a value in the keychain. This function is asynchronous.
+ */
+ void setKeychainValue(const QString &key, const QString &value) const;
+
+ /*
+ * Retrieves a value from the keychain. This function is synchronous.
+ */
+ QString getKeychainValue(const QString &key) const;
+
+ AccountConfig m_config;
+ QString m_key;
+ QUrl m_url;
+ LauncherCore &m_launcher;
+};
\ No newline at end of file
diff --git a/launcher/include/accountmanager.h b/launcher/include/accountmanager.h
new file mode 100644
index 0000000..bbee9f0
--- /dev/null
+++ b/launcher/include/accountmanager.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include
+
+#include "account.h"
+
+class AccountManager : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit AccountManager(LauncherCore &launcher, QObject *parent = nullptr);
+
+ void load();
+
+ enum CustomRoles {
+ AccountRole = Qt::UserRole,
+ };
+
+ int rowCount(const QModelIndex &index = QModelIndex()) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ QHash roleNames() const override;
+
+ Q_INVOKABLE Account *createSquareEnixAccount(const QString &username, int licenseType, bool isFreeTrial);
+ Q_INVOKABLE Account *createSapphireAccount(const QString &lobbyUrl, const QString &username);
+
+ Account *getByUuid(const QString &uuid) const;
+
+ Q_INVOKABLE bool canDelete(Account *account) const;
+ Q_INVOKABLE void deleteAccount(Account *account);
+
+Q_SIGNALS:
+
+private:
+ void insertAccount(Account *account);
+
+ QVector m_accounts;
+
+ LauncherCore &m_launcher;
+};
\ No newline at end of file
diff --git a/launcher/core/include/assetupdater.h b/launcher/include/assetupdater.h
similarity index 74%
rename from launcher/core/include/assetupdater.h
rename to launcher/include/assetupdater.h
index e9721c6..9b21f52 100644
--- a/launcher/core/include/assetupdater.h
+++ b/launcher/include/assetupdater.h
@@ -2,21 +2,20 @@
#include
#include
-#include
#include
#include "launchercore.h"
class LauncherCore;
class QNetworkReply;
-struct ProfileSettings;
-class AssetUpdater : public QObject {
+class AssetUpdater : public QObject
+{
Q_OBJECT
public:
- explicit AssetUpdater(LauncherCore& launcher);
+ explicit AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *parent = nullptr);
- void update(const ProfileSettings& profile);
+ void update();
void beginInstall();
void checkIfCheckingIsDone();
@@ -27,11 +26,9 @@ signals:
void finishedUpdating();
private:
- LauncherCore& launcher;
+ LauncherCore &launcher;
- QProgressDialog* dialog;
-
- DalamudChannel chosenChannel;
+ Profile::DalamudChannel chosenChannel;
QString remoteDalamudVersion;
QString remoteRuntimeVersion;
@@ -49,4 +46,5 @@ private:
QJsonArray remoteDalamudAssetArray;
QString dataDir;
+ Profile &m_profile;
};
diff --git a/launcher/include/encryptedarg.h b/launcher/include/encryptedarg.h
new file mode 100644
index 0000000..9e73592
--- /dev/null
+++ b/launcher/include/encryptedarg.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include
+
+QString encryptGameArg(const QString &arg);
\ No newline at end of file
diff --git a/launcher/include/gameinstaller.h b/launcher/include/gameinstaller.h
new file mode 100644
index 0000000..cb723a9
--- /dev/null
+++ b/launcher/include/gameinstaller.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include
+#include
+
+class LauncherCore;
+class Profile;
+
+class GameInstaller : public QObject
+{
+ Q_OBJECT
+public:
+ GameInstaller(LauncherCore &launcher, Profile &profile, QObject *parent = nullptr);
+
+ Q_INVOKABLE void installGame();
+
+Q_SIGNALS:
+ void installFinished();
+
+private:
+ LauncherCore &m_launcher;
+ Profile &m_profile;
+};
\ No newline at end of file
diff --git a/launcher/core/include/gameparser.h b/launcher/include/gameparser.h
similarity index 70%
rename from launcher/core/include/gameparser.h
rename to launcher/include/gameparser.h
index 5bfc7e1..d46f00c 100644
--- a/launcher/core/include/gameparser.h
+++ b/launcher/include/gameparser.h
@@ -4,14 +4,7 @@
#include
#include
-enum class ScreenState {
- Splash,
- LobbyError,
- WorldFull,
- ConnectingToDataCenter,
- EnteredTitleScreen,
- InLoginQueue
-};
+enum class ScreenState { Splash, LobbyError, WorldFull, ConnectingToDataCenter, EnteredTitleScreen, InLoginQueue };
struct GameParseResult {
ScreenState state;
@@ -19,15 +12,18 @@ struct GameParseResult {
int playersInQueue = -1;
};
-inline bool operator==(const GameParseResult a, const GameParseResult b) {
+inline bool operator==(const GameParseResult a, const GameParseResult b)
+{
return a.state == b.state && a.playersInQueue == b.playersInQueue;
}
-inline bool operator!=(const GameParseResult a, const GameParseResult b) {
+inline bool operator!=(const GameParseResult a, const GameParseResult b)
+{
return !(a == b);
}
-class GameParser {
+class GameParser
+{
public:
GameParser();
~GameParser();
@@ -35,5 +31,5 @@ public:
GameParseResult parseImage(QImage image);
private:
- tesseract::TessBaseAPI* api;
+ tesseract::TessBaseAPI *api;
};
\ No newline at end of file
diff --git a/launcher/include/headline.h b/launcher/include/headline.h
new file mode 100644
index 0000000..c56090b
--- /dev/null
+++ b/launcher/include/headline.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include
+#include
+#include
+
+class News
+{
+ Q_GADGET
+
+ Q_PROPERTY(QDateTime date MEMBER date CONSTANT)
+ Q_PROPERTY(QString id MEMBER id CONSTANT)
+ Q_PROPERTY(QString tag MEMBER tag CONSTANT)
+ Q_PROPERTY(QString title MEMBER title CONSTANT)
+ Q_PROPERTY(QUrl url MEMBER url CONSTANT)
+
+public:
+ QDateTime date;
+ QString id;
+ QString tag;
+ QString title;
+ QUrl url;
+};
+
+class Banner
+{
+ Q_GADGET
+
+ Q_PROPERTY(QUrl link MEMBER link CONSTANT)
+ Q_PROPERTY(QUrl bannerImage MEMBER bannerImage CONSTANT)
+
+public:
+ QUrl link;
+ QUrl bannerImage;
+};
+
+class Headline : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QList banners MEMBER banners CONSTANT)
+ Q_PROPERTY(QList news MEMBER news CONSTANT)
+ Q_PROPERTY(QList pinned MEMBER pinned CONSTANT)
+ Q_PROPERTY(QList topics MEMBER topics CONSTANT)
+
+public:
+ explicit Headline(QObject *parent = nullptr)
+ : QObject(parent)
+ {
+ }
+
+ QList banners;
+ QList news;
+ QList pinned;
+ QList topics;
+};
\ No newline at end of file
diff --git a/launcher/include/launchercore.h b/launcher/include/launchercore.h
new file mode 100755
index 0000000..af27af4
--- /dev/null
+++ b/launcher/include/launchercore.h
@@ -0,0 +1,182 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "accountmanager.h"
+#include "headline.h"
+#include "profile.h"
+#include "profilemanager.h"
+#include "squareboot.h"
+#include "steamapi.h"
+
+class SapphireLauncher;
+class SquareLauncher;
+class AssetUpdater;
+class Watchdog;
+class GameInstaller;
+
+class LoginInformation : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString username MEMBER username)
+ Q_PROPERTY(QString password MEMBER password)
+ Q_PROPERTY(QString oneTimePassword MEMBER oneTimePassword)
+ Q_PROPERTY(Profile *profile MEMBER profile)
+
+public:
+ Profile *profile = nullptr;
+
+ 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 LauncherCore : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool loadingFinished READ isLoadingFinished NOTIFY loadingFinished)
+ Q_PROPERTY(bool hasAccount READ hasAccount NOTIFY accountChanged)
+ Q_PROPERTY(bool isSteam READ isSteam CONSTANT)
+ Q_PROPERTY(SquareBoot *squareBoot MEMBER squareBoot)
+ Q_PROPERTY(ProfileManager *profileManager READ profileManager CONSTANT)
+ Q_PROPERTY(AccountManager *accountManager READ accountManager CONSTANT)
+ Q_PROPERTY(bool closeWhenLaunched READ closeWhenLaunched WRITE setCloseWhenLaunched NOTIFY closeWhenLaunchedChanged)
+ Q_PROPERTY(bool showNewsBanners READ showNewsBanners WRITE setShowNewsBanners NOTIFY showNewsBannersChanged)
+ Q_PROPERTY(bool showNewsList READ showNewsList WRITE setShowNewsList NOTIFY showNewsListChanged)
+ Q_PROPERTY(Headline *headline READ headline NOTIFY newsChanged)
+
+public:
+ explicit LauncherCore(bool isSteam);
+
+ QNetworkAccessManager *mgr;
+
+ ProfileManager *profileManager();
+ AccountManager *accountManager();
+
+ /*
+ * Begins the login process, and may call SquareBoot or SapphireLauncher depending on the profile type.
+ * It's designed to be opaque as possible to the caller.
+ *
+ * The login process is asynchronous.
+ */
+ Q_INVOKABLE void login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword);
+
+ /*
+ * Attempts to log into a profile without LoginInformation, which may or may not work depending on a combination of
+ * the password failing, OTP not being available to auto-generate, among other things.
+ *
+ * The launcher will still warn the user about any possible errors, however the call site will need to check the
+ * result to see whether they need to "reset" or show a failed state or not.
+ */
+ bool autoLogin(Profile &settings);
+
+ /*
+ * Launches the game using the provided authentication.
+ */
+ void launchGame(const Profile &settings, const LoginAuth &auth);
+
+ /*
+ * This just wraps it in wine if needed.
+ */
+ void launchExecutable(const Profile &settings, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup);
+
+ void addRegistryKey(const Profile &settings, QString key, QString value, QString data);
+
+ void buildRequest(const Profile &settings, QNetworkRequest &request);
+ void setSSL(QNetworkRequest &request);
+ void readInitialInformation();
+
+ SapphireLauncher *sapphireLauncher;
+ SquareBoot *squareBoot;
+ SquareLauncher *squareLauncher;
+ AssetUpdater *assetUpdater;
+ Watchdog *watchdog;
+
+ bool gamescopeAvailable = false;
+ bool gamemodeAvailable = false;
+
+ bool closeWhenLaunched() const;
+ void setCloseWhenLaunched(bool value);
+
+ bool showNewsBanners() const;
+ void setShowNewsBanners(bool value);
+
+ bool showNewsList() const;
+ void setShowNewsList(bool value);
+
+ int defaultProfileIndex = 0;
+
+ bool m_isSteam = false;
+
+ Q_INVOKABLE GameInstaller *createInstaller(Profile &profile);
+
+ bool isLoadingFinished() const;
+ bool hasAccount() const;
+ bool isSteam() const;
+
+ Q_INVOKABLE void refreshNews();
+ Headline *headline();
+
+ Q_INVOKABLE void openOfficialLauncher(Profile *profile);
+ Q_INVOKABLE void openSystemInfo(Profile *profile);
+ Q_INVOKABLE void openConfigBackup(Profile *profile);
+
+signals:
+ void loadingFinished();
+ void gameInstallationChanged();
+ void accountChanged();
+ void settingsChanged();
+ void successfulLaunch();
+ void gameClosed();
+ void closeWhenLaunchedChanged();
+ void showNewsBannersChanged();
+ void showNewsListChanged();
+ void loginError(QString message);
+ void stageChanged(QString message);
+ void newsChanged();
+
+private:
+ /*
+ * Begins the game executable, but calls to Dalamud if needed.
+ */
+ void beginGameExecutable(const Profile &settings, const LoginAuth &auth);
+
+ /*
+ * Starts a vanilla game session with no Dalamud injection.
+ */
+ void beginVanillaGame(const QString &gameExecutablePath, const Profile &profile, const LoginAuth &auth);
+
+ /*
+ * Starts a game session with Dalamud injected.
+ */
+ void beginDalamudGame(const QString &gameExecutablePath, const Profile &profile, const LoginAuth &auth);
+
+ /*
+ * Returns the game arguments needed to properly launch the game. This encrypts it too if needed, and it's already
+ * joined!
+ */
+ QString getGameArgs(const Profile &profile, const LoginAuth &auth);
+
+ bool checkIfInPath(const QString &program);
+
+ SteamAPI *steamApi = nullptr;
+
+ bool m_loadingFinished = false;
+
+ ProfileManager *m_profileManager = nullptr;
+ AccountManager *m_accountManager = nullptr;
+
+ Headline *m_headline = nullptr;
+};
diff --git a/launcher/core/include/patcher.h b/launcher/include/patcher.h
similarity index 52%
rename from launcher/core/include/patcher.h
rename to launcher/include/patcher.h
index d48473c..962b52e 100644
--- a/launcher/core/include/patcher.h
+++ b/launcher/include/patcher.h
@@ -1,19 +1,19 @@
#pragma once
#include
-#include
#include
#include
// General-purpose patcher routine. It opens a nice dialog box, handles downloading
// and processing patches.
-class Patcher : public QObject {
+class Patcher : public QObject
+{
Q_OBJECT
public:
- Patcher(QString baseDirectory, GameData* game_data);
- Patcher(QString baseDirectory, BootData* game_data);
+ Patcher(QString baseDirectory, GameData *game_data, QObject *parent = nullptr);
+ Patcher(QString baseDirectory, BootData *game_data, QObject *parent = nullptr);
- void processPatchList(QNetworkAccessManager& mgr, const QString& patchList);
+ void processPatchList(QNetworkAccessManager &mgr, const QString &patchList);
signals:
void done();
@@ -21,7 +21,8 @@ signals:
private:
void checkIfDone();
- [[nodiscard]] bool isBoot() const {
+ [[nodiscard]] bool isBoot() const
+ {
return boot_data != nullptr;
}
@@ -29,15 +30,13 @@ private:
QString name, repository, version, path;
};
- void processPatch(const QueuedPatch& patch);
+ void processPatch(const QueuedPatch &patch);
QVector patchQueue;
QString baseDirectory;
- BootData* boot_data = nullptr;
- GameData* game_data = nullptr;
-
- QProgressDialog* dialog = nullptr;
+ BootData *boot_data = nullptr;
+ GameData *game_data = nullptr;
int remainingPatches = -1;
};
\ No newline at end of file
diff --git a/launcher/include/profile.h b/launcher/include/profile.h
new file mode 100644
index 0000000..569d31a
--- /dev/null
+++ b/launcher/include/profile.h
@@ -0,0 +1,181 @@
+#pragma once
+
+#include
+
+#include "profileconfig.h"
+#include "squareboot.h"
+
+class Account;
+
+class Profile : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+ Q_PROPERTY(int language READ language WRITE setLanguage NOTIFY languageChanged)
+ Q_PROPERTY(QString gamePath READ gamePath WRITE setGamePath NOTIFY gamePathChanged)
+ Q_PROPERTY(QString winePath READ winePath WRITE setWinePath NOTIFY winePathChanged)
+ Q_PROPERTY(QString winePrefixPath READ winePrefixPath WRITE setWinePrefixPath NOTIFY winePrefixPathChanged)
+ Q_PROPERTY(bool watchdogEnabled READ watchdogEnabled WRITE setWatchdogEnabled NOTIFY enableWatchdogChanged)
+ Q_PROPERTY(WineType wineType READ wineType WRITE setWineType NOTIFY wineTypeChanged)
+ Q_PROPERTY(bool esyncEnabled READ esyncEnabled WRITE setESyncEnabled NOTIFY useESyncChanged)
+ Q_PROPERTY(bool gamescopeEnabled READ gamescopeEnabled WRITE setGamescopeEnabled NOTIFY useGamescopeChanged)
+ Q_PROPERTY(bool gamemodeEnabled READ gamemodeEnabled WRITE setGamemodeEnabled NOTIFY useGamemodeChanged)
+ Q_PROPERTY(bool directx9Enabled READ directx9Enabled WRITE setDirectX9Enabled NOTIFY useDX9Changed)
+ Q_PROPERTY(bool gamescopeFullscreen READ gamescopeFullscreen WRITE setGamescopeFullscreen NOTIFY gamescopeFullscreenChanged)
+ Q_PROPERTY(bool gamescopeBorderless READ gamescopeBorderless WRITE setGamescopeBorderless NOTIFY gamescopeBorderlessChanged)
+ Q_PROPERTY(int gamescopeWidth READ gamescopeWidth WRITE setGamescopeWidth NOTIFY gamescopeWidthChanged)
+ Q_PROPERTY(int gamescopeHeight READ gamescopeHeight WRITE setGamescopeHeight NOTIFY gamescopeHeightChanged)
+ Q_PROPERTY(int gamescopeRefreshRate READ gamescopeRefreshRate WRITE setGamescopeRefreshRate NOTIFY gamescopeRefreshRateChanged)
+ Q_PROPERTY(bool dalamudEnabled READ dalamudEnabled WRITE setDalamudEnabled NOTIFY dalamudEnabledChanged)
+ Q_PROPERTY(bool dalamudOptOut READ dalamudOptOut WRITE setDalamudOptOut NOTIFY dalamudOptOutChanged)
+ Q_PROPERTY(DalamudChannel dalamudChannel READ dalamudChannel WRITE setDalamudChannel NOTIFY dalamudChannelChanged)
+ Q_PROPERTY(bool argumentsEncrypted READ argumentsEncrypted WRITE setArgumentsEncrypted NOTIFY encryptedArgumentsChanged)
+ Q_PROPERTY(bool isGameInstalled READ isGameInstalled NOTIFY gameInstallChanged)
+ Q_PROPERTY(Account *account READ account WRITE setAccount NOTIFY accountChanged)
+ Q_PROPERTY(QString expansionVersionText READ expansionVersionText NOTIFY gameInstallChanged)
+ Q_PROPERTY(QString dalamudVersionText READ dalamudVersionText NOTIFY gameInstallChanged)
+ Q_PROPERTY(QString wineVersionText READ wineVersionText NOTIFY wineChanged)
+
+public:
+ explicit Profile(LauncherCore &launcher, const QString &key, QObject *parent = nullptr);
+
+ enum class WineType {
+ System,
+ Custom,
+ Builtin, // macos only
+ XIVOnMac // macos only
+ };
+ Q_ENUM(WineType)
+
+ enum class DalamudChannel { Stable, Staging, Net5 };
+ Q_ENUM(DalamudChannel)
+
+ QString uuid() const;
+
+ QString name() const;
+ void setName(const QString &name);
+
+ int language() const;
+ void setLanguage(int value);
+
+ QString gamePath() const;
+ void setGamePath(const QString &path);
+
+ QString winePath() const;
+ void setWinePath(const QString &path);
+
+ QString winePrefixPath() const;
+ void setWinePrefixPath(const QString &path);
+
+ bool watchdogEnabled() const;
+ void setWatchdogEnabled(bool value);
+
+ WineType wineType() const;
+ void setWineType(WineType type);
+
+ bool esyncEnabled() const;
+ void setESyncEnabled(bool value);
+
+ bool gamescopeEnabled() const;
+ void setGamescopeEnabled(bool value);
+
+ bool gamemodeEnabled() const;
+ void setGamemodeEnabled(bool value);
+
+ bool directx9Enabled() const;
+ void setDirectX9Enabled(bool value);
+
+ bool gamescopeFullscreen() const;
+ void setGamescopeFullscreen(bool value);
+
+ bool gamescopeBorderless() const;
+ void setGamescopeBorderless(bool value);
+
+ int gamescopeWidth() const;
+ void setGamescopeWidth(int value);
+
+ int gamescopeHeight() const;
+ void setGamescopeHeight(int value);
+
+ int gamescopeRefreshRate() const;
+ void setGamescopeRefreshRate(int value);
+
+ bool dalamudEnabled() const;
+ void setDalamudEnabled(bool value);
+
+ bool dalamudOptOut() const;
+ void setDalamudOptOut(bool value);
+
+ DalamudChannel dalamudChannel() const;
+ void setDalamudChannel(DalamudChannel channel);
+
+ bool argumentsEncrypted() const;
+ void setArgumentsEncrypted(bool value);
+
+ Account *account() const;
+ QString accountUuid() const;
+ void setAccount(Account *account);
+
+ void readGameData();
+ void readGameVersion();
+ void readWineInfo();
+
+ QVector expansionNames;
+
+ BootData *bootData;
+ GameData *gameData;
+
+ physis_Repositories repositories;
+ const char *bootVersion;
+
+ QString dalamudVersion;
+ int dalamudAssetVersion = -1;
+ QString runtimeVersion;
+
+ QString expansionVersionText() const;
+ QString dalamudVersionText() const;
+ QString wineVersionText() const;
+
+ [[nodiscard]] bool isGameInstalled() const
+ {
+ return repositories.repositories_count > 0;
+ }
+
+ [[nodiscard]] bool isWineInstalled() const
+ {
+ return !m_wineVersion.isEmpty();
+ }
+
+Q_SIGNALS:
+ void gameInstallChanged();
+ void nameChanged();
+ void languageChanged();
+ void gamePathChanged();
+ void winePathChanged();
+ void winePrefixPathChanged();
+ void enableWatchdogChanged();
+ void wineTypeChanged();
+ void useESyncChanged();
+ void useGamescopeChanged();
+ void useGamemodeChanged();
+ void useDX9Changed();
+ void gamescopeFullscreenChanged();
+ void gamescopeBorderlessChanged();
+ void gamescopeWidthChanged();
+ void gamescopeHeightChanged();
+ void gamescopeRefreshRateChanged();
+ void dalamudEnabledChanged();
+ void dalamudOptOutChanged();
+ void dalamudChannelChanged();
+ void encryptedArgumentsChanged();
+ void accountChanged();
+ void wineChanged();
+
+private:
+ QString m_uuid;
+ QString m_wineVersion;
+ ProfileConfig m_config;
+ Account *m_account = nullptr;
+ LauncherCore &m_launcher;
+};
\ No newline at end of file
diff --git a/launcher/include/profilemanager.h b/launcher/include/profilemanager.h
new file mode 100644
index 0000000..945e214
--- /dev/null
+++ b/launcher/include/profilemanager.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include
+
+#include "profile.h"
+
+class ProfileManager : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit ProfileManager(LauncherCore &launcher, QObject *parent = nullptr);
+
+ void load();
+
+ enum CustomRoles {
+ ProfileRole = Qt::UserRole,
+ };
+
+ int rowCount(const QModelIndex &index = QModelIndex()) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ QHash roleNames() const override;
+
+ Q_INVOKABLE Profile *getProfile(int index);
+
+ int getProfileIndex(const QString &name);
+ Q_INVOKABLE Profile *addProfile();
+ Q_INVOKABLE void deleteProfile(Profile *profile);
+
+ QVector profiles() const;
+
+ Q_INVOKABLE bool canDelete(Profile *account) const;
+
+private:
+ void insertProfile(Profile *profile);
+
+ QString getDefaultGamePath();
+ QString getDefaultWinePrefixPath();
+
+ QVector m_profiles;
+
+ LauncherCore &m_launcher;
+};
\ No newline at end of file
diff --git a/launcher/include/sapphirelauncher.h b/launcher/include/sapphirelauncher.h
new file mode 100644
index 0000000..d1dba6b
--- /dev/null
+++ b/launcher/include/sapphirelauncher.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include
+
+#include "launchercore.h"
+
+class SapphireLauncher : QObject
+{
+public:
+ explicit SapphireLauncher(LauncherCore &window, QObject *parent = nullptr);
+
+ void login(const QString &lobbyUrl, const LoginInformation &info);
+ void registerAccount(const QString &lobbyUrl, const LoginInformation &info);
+
+private:
+ LauncherCore &window;
+};
\ No newline at end of file
diff --git a/launcher/include/squareboot.h b/launcher/include/squareboot.h
new file mode 100644
index 0000000..57d1824
--- /dev/null
+++ b/launcher/include/squareboot.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "patcher.h"
+
+class SquareLauncher;
+class LauncherCore;
+class LoginInformation;
+
+class SquareBoot : public QObject
+{
+ Q_OBJECT
+public:
+ SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *parent = nullptr);
+
+ Q_INVOKABLE void checkGateStatus(LoginInformation *info);
+
+ void bootCheck(const LoginInformation &info);
+
+private:
+ Patcher *patcher = nullptr;
+
+ LauncherCore &window;
+ SquareLauncher &launcher;
+};
\ No newline at end of file
diff --git a/launcher/include/squarelauncher.h b/launcher/include/squarelauncher.h
new file mode 100644
index 0000000..b4cd631
--- /dev/null
+++ b/launcher/include/squarelauncher.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "launchercore.h"
+#include "patcher.h"
+
+class SquareLauncher : public QObject
+{
+ Q_OBJECT
+public:
+ explicit SquareLauncher(LauncherCore &window, QObject *parent = nullptr);
+
+ void getStored(const LoginInformation &info);
+
+ void login(const LoginInformation &info, const QUrl &referer);
+
+ void registerSession(const LoginInformation &info);
+
+private:
+ QString getBootHash(const LoginInformation &info);
+
+ Patcher *patcher = nullptr;
+
+ QString stored, SID, username;
+ LoginAuth auth;
+
+ LauncherCore &window;
+};
diff --git a/launcher/include/steamapi.h b/launcher/include/steamapi.h
new file mode 100644
index 0000000..f3d5455
--- /dev/null
+++ b/launcher/include/steamapi.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+
+class LauncherCore;
+
+class SteamAPI : public QObject
+{
+public:
+ explicit SteamAPI(LauncherCore &core, QObject *parent = nullptr);
+
+ void setLauncherMode(bool isLauncher);
+
+ [[nodiscard]] bool isDeck() const;
+
+private:
+ LauncherCore &core;
+};
\ No newline at end of file
diff --git a/launcher/core/include/watchdog.h b/launcher/include/watchdog.h
similarity index 50%
rename from launcher/core/include/watchdog.h
rename to launcher/include/watchdog.h
index 1b3aacb..4b7f866 100644
--- a/launcher/core/include/watchdog.h
+++ b/launcher/include/watchdog.h
@@ -5,21 +5,26 @@
#include "launchercore.h"
#if defined(Q_OS_LINUX)
- #include "gameparser.h"
+#include "gameparser.h"
#endif
#include
-class Watchdog : public QObject {
+class Watchdog : public QObject
+{
Q_OBJECT
public:
- Watchdog(LauncherCore& core) : core(core), QObject(&core) {}
+ Watchdog(LauncherCore &core)
+ : core(core)
+ , QObject(&core)
+ {
+ }
- void launchGame(const ProfileSettings& settings, const LoginAuth& auth);
+ void launchGame(const ProfileSettings &settings, const LoginAuth &auth);
private:
- LauncherCore& core;
- QSystemTrayIcon* icon = nullptr;
+ LauncherCore &core;
+ QSystemTrayIcon *icon = nullptr;
int processWindowId = -1;
diff --git a/launcher/main.cpp b/launcher/main.cpp
deleted file mode 100755
index 3ec4fba..0000000
--- a/launcher/main.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-#include "launchercore.h"
-
-#include
-#include
-
-#include "config.h"
-#include "desktopinterface.h"
-#include "sapphirelauncher.h"
-#include "squareboot.h"
-
-int main(int argc, char* argv[]) {
- QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
- QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
-
- QApplication app(argc, argv);
-
- QCoreApplication::setApplicationName("astra");
- QCoreApplication::setApplicationVersion(version);
-
- QCommandLineParser parser;
- parser.setApplicationDescription("Cross-platform FFXIV Launcher");
-
- auto helpOption = parser.addHelpOption();
- auto versionOption = parser.addVersionOption();
-
- QCommandLineOption steamOption("steam", "Simulate booting the launcher via Steam.");
-#ifdef ENABLE_STEAM
- parser.addOption(steamOption);
-#endif
-
- parser.process(app);
-
- if (parser.isSet(versionOption)) {
- parser.showVersion();
- }
-
- if (parser.isSet(helpOption)) {
- parser.showHelp();
- }
-
-#ifdef ENABLE_STEAM
- LauncherCore c(parser.isSet(steamOption));
-#else
- LauncherCore c(false);
-#endif
- std::make_unique(c);
-
- return QApplication::exec();
-}
diff --git a/launcher/profileconfig.kcfg b/launcher/profileconfig.kcfg
new file mode 100644
index 0000000..877744d
--- /dev/null
+++ b/launcher/profileconfig.kcfg
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ System
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ 1280
+
+
+ 720
+
+
+ 60
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ Stable
+
+
+ true
+
+
+
diff --git a/launcher/profileconfig.kcfgc b/launcher/profileconfig.kcfgc
new file mode 100644
index 0000000..4a905de
--- /dev/null
+++ b/launcher/profileconfig.kcfgc
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2023 Joshua Goins
+# SPDX-License-Identifier: LGPL-2.1-or-later
+File=profileconfig.kcfg
+ClassName=ProfileConfig
+Mutators=true
+DefaultValueGetters=true
+GenerateProperties=true
+ParentInConstructor=true
+Singleton=false
diff --git a/launcher/resources.qrc b/launcher/resources.qrc
new file mode 100644
index 0000000..0a0ee2b
--- /dev/null
+++ b/launcher/resources.qrc
@@ -0,0 +1,21 @@
+
+
+ ui/Components/FormFileDelegate.qml
+ ui/Components/FormFolderDelegate.qml
+ ui/Pages/LoginPage.qml
+ ui/Pages/NewsPage.qml
+ ui/Pages/StatusPage.qml
+ ui/Settings/AccountSettings.qml
+ ui/Settings/GeneralSettings.qml
+ ui/Settings/ProfileSettings.qml
+ ui/Settings/SettingsPage.qml
+ ui/Setup/AccountSetup.qml
+ ui/Setup/AddSapphire.qml
+ ui/Setup/AddSquareEnix.qml
+ ui/Setup/DownloadSetup.qml
+ ui/Setup/ExistingSetup.qml
+ ui/Setup/InstallProgress.qml
+ ui/Setup/SetupPage.qml
+ ui/main.qml
+
+
\ No newline at end of file
diff --git a/launcher/src/account.cpp b/launcher/src/account.cpp
new file mode 100644
index 0000000..109c55b
--- /dev/null
+++ b/launcher/src/account.cpp
@@ -0,0 +1,222 @@
+#include "account.h"
+
+#include
+#include
+#include
+#include
+
+#include "cotp.h"
+#include "launchercore.h"
+
+Account::Account(LauncherCore &launcher, const QString &key, QObject *parent)
+ : QObject(parent)
+ , m_config(key)
+ , m_key(key)
+ , m_launcher(launcher)
+{
+ fetchAvatar();
+}
+
+QString Account::uuid() const
+{
+ return m_key;
+}
+
+QString Account::name() const
+{
+ return m_config.name();
+}
+
+void Account::setName(const QString &name)
+{
+ if (m_config.name() != name) {
+ m_config.setName(name);
+ m_config.save();
+ Q_EMIT nameChanged();
+ }
+}
+
+QString Account::lodestoneId() const
+{
+ return m_config.lodestoneId();
+}
+
+void Account::setLodestoneId(const QString &id)
+{
+ if (m_config.lodestoneId() != id) {
+ m_config.setLodestoneId(id);
+ m_config.save();
+ fetchAvatar();
+ Q_EMIT lodestoneIdChanged();
+ }
+}
+
+QString Account::avatarUrl() const
+{
+ return m_url.toString();
+}
+
+bool Account::isSapphire() const
+{
+ return m_config.isSapphire();
+}
+
+void Account::setIsSapphire(bool value)
+{
+ if (m_config.isSapphire() != value) {
+ m_config.setIsSapphire(value);
+ m_config.save();
+ Q_EMIT isSapphireChanged();
+ }
+}
+
+QString Account::lobbyUrl() const
+{
+ return m_config.lobbyUrl();
+}
+
+void Account::setLobbyUrl(const QString &value)
+{
+ if (m_config.lobbyUrl() != value) {
+ m_config.setLobbyUrl(value);
+ m_config.save();
+ Q_EMIT lobbyUrlChanged();
+ }
+}
+
+bool Account::rememberPassword() const
+{
+ return m_config.rememberPassword();
+}
+
+void Account::setRememberPassword(const bool value)
+{
+ if (m_config.rememberPassword() != value) {
+ m_config.setRememberPassword(value);
+ m_config.save();
+ Q_EMIT rememberPasswordChanged();
+ }
+}
+
+bool Account::rememberOTP() const
+{
+ return m_config.rememberOTP();
+}
+
+void Account::setRememberOTP(const bool value)
+{
+ if (m_config.rememberOTP() != value) {
+ m_config.setRememberOTP(value);
+ m_config.save();
+ Q_EMIT rememberOTPChanged();
+ }
+}
+
+bool Account::useOTP() const
+{
+ return m_config.useOTP();
+}
+
+void Account::setUseOTP(const bool value)
+{
+ if (m_config.useOTP() != value) {
+ m_config.setUseOTP(value);
+ m_config.save();
+ Q_EMIT useOTPChanged();
+ }
+}
+
+Account::GameLicense Account::license() const
+{
+ return static_cast(m_config.license());
+}
+
+void Account::setLicense(const GameLicense license)
+{
+ if (static_cast(m_config.license()) != license) {
+ m_config.setLicense(static_cast(license));
+ m_config.save();
+ Q_EMIT licenseChanged();
+ }
+}
+
+bool Account::isFreeTrial() const
+{
+ return m_config.isFreeTrial();
+}
+
+void Account::setIsFreeTrial(const bool value)
+{
+ if (m_config.isFreeTrial() != value) {
+ m_config.setIsFreeTrial(value);
+ m_config.save();
+ Q_EMIT isFreeTrialChanged();
+ }
+}
+
+QString Account::getPassword() const
+{
+ return getKeychainValue("password");
+}
+
+void Account::setPassword(const QString &password)
+{
+ setKeychainValue("password", password);
+}
+
+QString Account::getOTP() const
+{
+ auto otpSecret = getKeychainValue("otp-secret");
+
+ char *totp = get_totp(otpSecret.toStdString().c_str(), 6, 30, SHA1, nullptr);
+ QString totpStr(totp);
+ free(totp);
+
+ return totpStr;
+}
+
+void Account::fetchAvatar()
+{
+ if (lodestoneId().isEmpty()) {
+ return;
+ }
+
+ QNetworkRequest request(QStringLiteral("https://xivapi.com/character/%1").arg(lodestoneId()));
+ auto reply = m_launcher.mgr->get(request);
+ connect(reply, &QNetworkReply::finished, [this, reply] {
+ auto document = QJsonDocument::fromJson(reply->readAll());
+ if (document.isObject()) {
+ m_url = document.object()["Character"].toObject()["Avatar"].toString();
+ Q_EMIT avatarUrlChanged();
+ }
+ });
+}
+
+void Account::setKeychainValue(const QString &key, const QString &value) const
+{
+ auto job = new QKeychain::WritePasswordJob("Astra");
+ job->setTextData(value);
+ job->setKey(m_key + "-" + key);
+ job->start();
+}
+
+QString Account::getKeychainValue(const QString &key) const
+{
+ auto loop = new QEventLoop();
+
+ auto job = new QKeychain::ReadPasswordJob("Astra");
+ job->setKey(m_key + "-" + key);
+ job->start();
+
+ QString value;
+
+ QObject::connect(job, &QKeychain::ReadPasswordJob::finished, [loop, job, &value](QKeychain::Job *j) {
+ Q_UNUSED(j)
+ value = job->textData();
+ loop->quit();
+ });
+
+ loop->exec();
+
+ return value;
+}
diff --git a/launcher/src/accountmanager.cpp b/launcher/src/accountmanager.cpp
new file mode 100644
index 0000000..f16f8aa
--- /dev/null
+++ b/launcher/src/accountmanager.cpp
@@ -0,0 +1,106 @@
+#include "accountmanager.h"
+
+#include
+
+AccountManager::AccountManager(LauncherCore &launcher, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_launcher(launcher)
+{
+}
+
+void AccountManager::load()
+{
+ auto config = KSharedConfig::openStateConfig();
+ for (const auto &id : config->groupList()) {
+ if (id.contains("account-")) {
+ auto profile = new Account(m_launcher, QString(id).remove("account-"), this);
+ m_accounts.append(profile);
+ }
+ }
+}
+
+int AccountManager::rowCount(const QModelIndex &index) const
+{
+ Q_UNUSED(index);
+ return m_accounts.size();
+}
+
+QVariant AccountManager::data(const QModelIndex &index, int role) const
+{
+ if (!checkIndex(index)) {
+ return {};
+ }
+
+ const int row = index.row();
+ if (role == AccountRole) {
+ return QVariant::fromValue(m_accounts[row]);
+ }
+
+ return {};
+}
+
+QHash AccountManager::roleNames() const
+{
+ return {{AccountRole, QByteArrayLiteral("account")}};
+}
+
+Account *AccountManager::createSquareEnixAccount(const QString &username, int licenseType, bool isFreeTrial)
+{
+ auto account = new Account(m_launcher, QUuid::createUuid().toString(), this);
+ account->setIsSapphire(false);
+ account->setLicense(static_cast(licenseType));
+ account->setIsFreeTrial(isFreeTrial);
+ account->setName(username);
+
+ insertAccount(account);
+
+ return account;
+}
+
+Account *AccountManager::createSapphireAccount(const QString &lobbyUrl, const QString &username)
+{
+ auto account = new Account(m_launcher, QUuid::createUuid().toString(), this);
+ account->setIsSapphire(true);
+ account->setName(username);
+ account->setLobbyUrl(lobbyUrl);
+
+ insertAccount(account);
+
+ return account;
+}
+
+Account *AccountManager::getByUuid(const QString &uuid) const
+{
+ for (auto &account : m_accounts) {
+ if (account->uuid() == uuid) {
+ return account;
+ }
+ }
+
+ return nullptr;
+}
+
+bool AccountManager::canDelete(Account *account) const
+{
+ Q_UNUSED(account)
+ return m_accounts.size() != 1;
+}
+
+void AccountManager::deleteAccount(Account *account)
+{
+ auto config = KSharedConfig::openStateConfig();
+ config->deleteGroup(QString("account-%1").arg(account->uuid()));
+ config->sync();
+
+ const int row = m_accounts.indexOf(account);
+ beginRemoveRows(QModelIndex(), row, row);
+ m_accounts.removeAll(account);
+ endRemoveRows();
+}
+
+void AccountManager::insertAccount(Account *account)
+{
+ beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size());
+ m_accounts.append(account);
+ endInsertRows();
+}
diff --git a/launcher/core/src/assetupdater.cpp b/launcher/src/assetupdater.cpp
similarity index 76%
rename from launcher/core/src/assetupdater.cpp
rename to launcher/src/assetupdater.cpp
index 8521d36..bd6776a 100644
--- a/launcher/core/src/assetupdater.cpp
+++ b/launcher/src/assetupdater.cpp
@@ -3,14 +3,11 @@
#include
#include
#include
-#include
#include
#include
#include
-#include "launchercore.h"
-
const QString baseGoatDomain = "https://goatcorp.github.io";
const QString baseDalamudDistribution = baseGoatDomain + "/dalamud-distrib/";
@@ -20,17 +17,18 @@ const QString dalamudVersionManifestURL = baseDalamudDistribution + "%1version";
const QString baseDalamudAssetDistribution = baseGoatDomain + "/DalamudAssets";
const QString dalamudAssetManifestURL = baseDalamudAssetDistribution + "/asset.json";
-const QString dotnetRuntimePackageURL =
- "https://dotnetcli.azureedge.net/dotnet/Runtime/%1/dotnet-runtime-%1-win-x64.zip";
-const QString dotnetDesktopPackageURL =
- "https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/%1/windowsdesktop-runtime-%1-win-x64.zip";
+const QString dotnetRuntimePackageURL = "https://dotnetcli.azureedge.net/dotnet/Runtime/%1/dotnet-runtime-%1-win-x64.zip";
+const QString dotnetDesktopPackageURL = "https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/%1/windowsdesktop-runtime-%1-win-x64.zip";
-QMap channelToDistribPrefix = {
- {DalamudChannel::Stable, "/"},
- {DalamudChannel::Staging, "stg/"},
- {DalamudChannel::Net5, "net5/"}};
+QMap channelToDistribPrefix = {{Profile::DalamudChannel::Stable, "/"},
+ {Profile::DalamudChannel::Staging, "stg/"},
+ {Profile::DalamudChannel::Net5, "net5/"}};
-AssetUpdater::AssetUpdater(LauncherCore& launcher) : launcher(launcher), QObject(&launcher) {
+AssetUpdater::AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *parent)
+ : QObject(parent)
+ , launcher(launcher)
+ , m_profile(profile)
+{
launcher.mgr->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
@@ -39,19 +37,20 @@ AssetUpdater::AssetUpdater(LauncherCore& launcher) : launcher(launcher), QObject
QDir().mkdir(dataDir);
}
-void AssetUpdater::update(const ProfileSettings& profile) {
+void AssetUpdater::update()
+{
// non-dalamud users can bypass this process since it's not needed
- if (!profile.dalamud.enabled) {
+ if (!m_profile.dalamudEnabled()) {
finishedUpdating();
return;
}
- dialog = new QProgressDialog("Updating assets...", "Cancel", 0, 0);
+ // dialog = new QProgressDialog("Updating assets...", "Cancel", 0, 0);
// first, we want to collect all of the remote versions
qInfo() << "Starting update sequence...";
- dialog->setLabelText("Checking for updates...");
+ // dialog->setLabelText("Checking for updates...");
// dalamud assets
{
@@ -65,14 +64,14 @@ void AssetUpdater::update(const ProfileSettings& profile) {
QNetworkRequest request(dalamudAssetManifestURL);
auto reply = launcher.mgr->get(request);
- connect(reply, &QNetworkReply::finished, [reply, this, &profile] {
- dialog->setLabelText("Checking for Dalamud asset updates...");
+ connect(reply, &QNetworkReply::finished, [reply, this] {
+ // dialog->setLabelText("Checking for Dalamud asset updates...");
// TODO: handle asset failure
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
qInfo() << "Dalamud asset remote version" << doc.object()["Version"].toInt();
- qInfo() << "Dalamud asset local version" << launcher.dalamudAssetVersion;
+ qInfo() << "Dalamud asset local version" << m_profile.dalamudAssetVersion;
remoteDalamudAssetVersion = doc.object()["Version"].toInt();
@@ -85,16 +84,16 @@ void AssetUpdater::update(const ProfileSettings& profile) {
// dalamud injector / net runtime
// they're all updated in unison, so there's no reason to have multiple checks
{
- QNetworkRequest request(dalamudVersionManifestURL.arg(channelToDistribPrefix[profile.dalamud.channel]));
+ QNetworkRequest request(dalamudVersionManifestURL.arg(channelToDistribPrefix[m_profile.dalamudChannel()]));
- chosenChannel = profile.dalamud.channel;
+ chosenChannel = m_profile.dalamudChannel();
remoteDalamudVersion.clear();
remoteRuntimeVersion.clear();
auto reply = launcher.mgr->get(request);
- connect(reply, &QNetworkReply::finished, [this, &profile, reply] {
- dialog->setLabelText("Checking for Dalamud updates...");
+ connect(reply, &QNetworkReply::finished, [this, reply] {
+ // dialog->setLabelText("Checking for Dalamud updates...");
QByteArray str = reply->readAll();
// for some god forsaken reason, the version string comes back as raw
@@ -119,7 +118,8 @@ void AssetUpdater::update(const ProfileSettings& profile) {
}
}
-void AssetUpdater::beginInstall() {
+void AssetUpdater::beginInstall()
+{
if (needsDalamudInstall) {
bool success = !JlCompress::extractDir(tempDir.path() + "/latest.zip", dataDir + "/Dalamud").empty();
@@ -132,8 +132,7 @@ void AssetUpdater::beginInstall() {
}
if (needsRuntimeInstall) {
- bool success =
- !JlCompress::extractDir(tempDir.path() + "/dotnet-core.zip", dataDir + "/DalamudRuntime").empty();
+ bool success = !JlCompress::extractDir(tempDir.path() + "/dotnet-core.zip", dataDir + "/DalamudRuntime").empty();
success |= !JlCompress::extractDir(tempDir.path() + "/dotnet-desktop.zip", dataDir + "/DalamudRuntime").empty();
@@ -152,14 +151,15 @@ void AssetUpdater::beginInstall() {
checkIfFinished();
}
-void AssetUpdater::checkIfDalamudAssetsDone() {
- if (dialog->wasCanceled())
- return;
+void AssetUpdater::checkIfDalamudAssetsDone()
+{
+ // if (dialog->wasCanceled())
+ // return;
if (dalamudAssetNeededFilenames.empty()) {
qInfo() << "Finished downloading Dalamud assets.";
- launcher.dalamudAssetVersion = remoteDalamudAssetVersion;
+ m_profile.dalamudAssetVersion = remoteDalamudAssetVersion;
QFile file(dataDir + "/DalamudAssets/" + "asset.ver");
file.open(QIODevice::WriteOnly | QIODevice::Text);
@@ -170,26 +170,27 @@ void AssetUpdater::checkIfDalamudAssetsDone() {
}
}
-void AssetUpdater::checkIfFinished() {
- if (dialog->wasCanceled())
- return;
+void AssetUpdater::checkIfFinished()
+{
+ // if (dialog->wasCanceled())
+ // return;
- if (doneDownloadingDalamud && doneDownloadingRuntimeCore &&
- doneDownloadingRuntimeDesktop && dalamudAssetNeededFilenames.empty()) {
+ if (doneDownloadingDalamud && doneDownloadingRuntimeCore && doneDownloadingRuntimeDesktop && dalamudAssetNeededFilenames.empty()) {
if (needsRuntimeInstall || needsDalamudInstall) {
beginInstall();
} else {
- dialog->setLabelText("Finished!");
- dialog->close();
+ // dialog->setLabelText("Finished!");
+ // dialog->close();
finishedUpdating();
}
}
}
-void AssetUpdater::checkIfCheckingIsDone() {
- if (dialog->wasCanceled())
- return;
+void AssetUpdater::checkIfCheckingIsDone()
+{
+ // if (dialog->wasCanceled())
+ // return;
if (remoteDalamudVersion.isEmpty() || remoteRuntimeVersion.isEmpty() || remoteDalamudAssetVersion == -1) {
return;
@@ -198,10 +199,10 @@ void AssetUpdater::checkIfCheckingIsDone() {
// now that we got all the information we need, let's check if anything is
// updateable
- dialog->setLabelText("Starting update...");
+ // dialog->setLabelText("Starting update...");
// dalamud injector / net runtime
- if (launcher.runtimeVersion != remoteRuntimeVersion) {
+ if (m_profile.runtimeVersion != remoteRuntimeVersion) {
needsRuntimeInstall = true;
// core
@@ -212,7 +213,7 @@ void AssetUpdater::checkIfCheckingIsDone() {
connect(reply, &QNetworkReply::finished, [this, reply] {
qInfo() << "Dotnet-core finished downloading!";
- dialog->setLabelText("Updating Dotnet-core...");
+ // dialog->setLabelText("Updating Dotnet-core...");
QFile file(tempDir.path() + "/dotnet-core.zip");
file.open(QIODevice::WriteOnly);
@@ -233,7 +234,7 @@ void AssetUpdater::checkIfCheckingIsDone() {
connect(reply, &QNetworkReply::finished, [this, reply] {
qInfo() << "Dotnet-desktop finished downloading!";
- dialog->setLabelText("Updating Dotnet-desktop...");
+ // dialog->setLabelText("Updating Dotnet-desktop...");
QFile file(tempDir.path() + "/dotnet-desktop.zip");
file.open(QIODevice::WriteOnly);
@@ -253,7 +254,7 @@ void AssetUpdater::checkIfCheckingIsDone() {
checkIfFinished();
}
- if (remoteDalamudVersion != launcher.dalamudVersion) {
+ if (remoteDalamudVersion != m_profile.dalamudVersion) {
qInfo() << "Downloading Dalamud...";
needsDalamudInstall = true;
@@ -264,7 +265,7 @@ void AssetUpdater::checkIfCheckingIsDone() {
connect(reply, &QNetworkReply::finished, [this, reply] {
qInfo() << "Dalamud finished downloading!";
- dialog->setLabelText("Updating Dalamud...");
+ // dialog->setLabelText("Updating Dalamud...");
QFile file(tempDir.path() + "/latest.zip");
file.open(QIODevice::WriteOnly);
@@ -273,7 +274,7 @@ void AssetUpdater::checkIfCheckingIsDone() {
doneDownloadingDalamud = true;
- launcher.dalamudVersion = remoteDalamudVersion;
+ m_profile.dalamudVersion = remoteDalamudVersion;
checkIfFinished();
});
@@ -287,10 +288,10 @@ void AssetUpdater::checkIfCheckingIsDone() {
}
// dalamud assets
- if (remoteDalamudAssetVersion != launcher.dalamudAssetVersion) {
+ if (remoteDalamudAssetVersion != m_profile.dalamudAssetVersion) {
qInfo() << "Dalamud assets out of date.";
- dialog->setLabelText("Updating Dalamud assets...");
+ // dialog->setLabelText("Updating Dalamud assets...");
dalamudAssetNeededFilenames.clear();
@@ -309,7 +310,7 @@ void AssetUpdater::checkIfCheckingIsDone() {
const QList dirPath = fileName.left(fileName.lastIndexOf("/")).split('/');
QString build = dataDir + "/DalamudAssets/";
- for (const auto& dir : dirPath) {
+ for (const auto &dir : dirPath) {
if (!QDir().exists(build + dir))
QDir().mkdir(build + dir);
diff --git a/launcher/src/encryptedarg.cpp b/launcher/src/encryptedarg.cpp
new file mode 100644
index 0000000..0004ea9
--- /dev/null
+++ b/launcher/src/encryptedarg.cpp
@@ -0,0 +1,81 @@
+#include "encryptedarg.h"
+
+#include
+
+#if defined(Q_OS_MAC)
+#include
+#include
+#endif
+
+#if defined(Q_OS_WIN)
+#include
+#endif
+
+#if defined(Q_OS_MAC)
+// taken from XIV-on-Mac, apparently Wine changed this?
+uint32_t TickCount()
+{
+ struct mach_timebase_info timebase;
+ mach_timebase_info(&timebase);
+
+ auto machtime = mach_continuous_time();
+ auto numer = uint64_t(timebase.numer);
+ auto denom = uint64_t(timebase.denom);
+ auto monotonic_time = machtime * numer / denom / 100;
+ return monotonic_time / 10000;
+}
+#endif
+
+#if defined(Q_OS_LINUX)
+uint32_t TickCount()
+{
+ struct timespec ts {
+ };
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
+}
+#endif
+
+#if defined(Q_OS_WIN)
+uint32_t TickCount()
+{
+ return GetTickCount();
+}
+#endif
+
+// from xivdev
+static char ChecksumTable[] = {'f', 'X', '1', 'p', 'G', 't', 'd', 'S', '5', 'C', 'A', 'P', '4', '_', 'V', 'L'};
+
+inline char GetChecksum(const unsigned int key)
+{
+ auto value = key & 0x000F0000;
+ return ChecksumTable[value >> 16];
+}
+
+QString encryptGameArg(const QString &arg)
+{
+ const uint32_t rawTicks = TickCount();
+ const uint32_t ticks = rawTicks & 0xFFFFFFFFu;
+ const uint32_t key = ticks & 0xFFFF0000u;
+
+ char buffer[9]{};
+ sprintf(buffer, "%08x", key);
+
+ Blowfish const *blowfish = physis_blowfish_initialize(reinterpret_cast(buffer), 9);
+
+ uint8_t *out_data = nullptr;
+ uint32_t out_size = 0;
+
+ QByteArray toEncrypt = (QString(" /T =%1").arg(ticks) + arg).toUtf8();
+
+ physis_blowfish_encrypt(blowfish, reinterpret_cast(toEncrypt.data()), toEncrypt.size(), &out_data, &out_size);
+
+ const QByteArray encryptedArg = QByteArray::fromRawData(reinterpret_cast(out_data), static_cast(out_size));
+
+ const QString base64 = encryptedArg.toBase64(QByteArray::Base64Option::Base64UrlEncoding | QByteArray::Base64Option::KeepTrailingEquals);
+ const char checksum = GetChecksum(key);
+
+ return QString("//**sqex0003%1%2**//").arg(base64, QString(checksum));
+}
\ No newline at end of file
diff --git a/launcher/core/src/gameinstaller.cpp b/launcher/src/gameinstaller.cpp
similarity index 66%
rename from launcher/core/src/gameinstaller.cpp
rename to launcher/src/gameinstaller.cpp
index 8ec1df9..a9ee79e 100644
--- a/launcher/core/src/gameinstaller.cpp
+++ b/launcher/src/gameinstaller.cpp
@@ -6,17 +6,25 @@
#include
#include "launchercore.h"
+#include "profile.h"
-void installGame(LauncherCore& launcher, ProfileSettings& profile, const std::function& returnFunc) {
- QString installDirectory = profile.gamePath;
+GameInstaller::GameInstaller(LauncherCore &launcher, Profile &profile, QObject *parent)
+ : QObject(parent)
+ , m_launcher(launcher)
+ , m_profile(profile)
+{
+}
+
+void GameInstaller::installGame()
+{
+ const QString installDirectory = m_profile.gamePath();
qDebug() << "Installing game to " << installDirectory << "!";
-
qDebug() << "Now downloading installer file...";
QNetworkRequest request(QUrl("https://gdl.square-enix.com/ffxiv/inst/ffxivsetup.exe"));
- auto reply = launcher.mgr->get(request);
- QObject::connect(reply, &QNetworkReply::finished, [reply, installDirectory, returnFunc] {
+ auto reply = m_launcher.mgr->get(request);
+ QObject::connect(reply, &QNetworkReply::finished, [this, reply, installDirectory] {
QString dataDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QFile file(dataDir + "/ffxivsetup.exe");
@@ -31,6 +39,6 @@ void installGame(LauncherCore& launcher, ProfileSettings& profile, const std::fu
qDebug() << "Done installing to " << installDirectory << "!";
- returnFunc();
+ Q_EMIT installFinished();
});
-}
\ No newline at end of file
+}
diff --git a/launcher/core/src/gameparser.cpp b/launcher/src/gameparser.cpp
similarity index 93%
rename from launcher/core/src/gameparser.cpp
rename to launcher/src/gameparser.cpp
index eb38cc8..92fdad5 100644
--- a/launcher/core/src/gameparser.cpp
+++ b/launcher/src/gameparser.cpp
@@ -4,7 +4,8 @@
#include
#include
-GameParser::GameParser() {
+GameParser::GameParser()
+{
api = new tesseract::TessBaseAPI();
if (api->Init(nullptr, "eng")) {
@@ -15,17 +16,19 @@ GameParser::GameParser() {
api->SetPageSegMode(tesseract::PageSegMode::PSM_SINGLE_BLOCK);
}
-GameParser::~GameParser() {
+GameParser::~GameParser()
+{
api->End();
delete api;
}
-GameParseResult GameParser::parseImage(QImage img) {
+GameParseResult GameParser::parseImage(QImage img)
+{
QBuffer buf;
img = img.convertToFormat(QImage::Format_Grayscale8);
img.save(&buf, "PNG", 100);
- Pix* image = pixReadMem((const l_uint8*)buf.data().data(), buf.size());
+ Pix *image = pixReadMem((const l_uint8 *)buf.data().data(), buf.size());
api->SetImage(image);
api->SetSourceResolution(300);
diff --git a/launcher/src/launchercore.cpp b/launcher/src/launchercore.cpp
new file mode 100755
index 0000000..bf0e192
--- /dev/null
+++ b/launcher/src/launchercore.cpp
@@ -0,0 +1,580 @@
+#include "gameinstaller.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef ENABLE_GAMEMODE
+#include
+#endif
+
+#include "account.h"
+#include "assetupdater.h"
+#include "config.h"
+#include "encryptedarg.h"
+#include "launchercore.h"
+#include "sapphirelauncher.h"
+#include "squarelauncher.h"
+
+#ifdef ENABLE_WATCHDOG
+#include "watchdog.h"
+#endif
+
+void LauncherCore::setSSL(QNetworkRequest &request)
+{
+ QSslConfiguration config;
+ config.setProtocol(QSsl::AnyProtocol);
+ config.setPeerVerifyMode(QSslSocket::VerifyNone);
+
+ request.setSslConfiguration(config);
+}
+
+void LauncherCore::buildRequest(const Profile &settings, QNetworkRequest &request)
+{
+ setSSL(request);
+
+ if (settings.account()->license() == Account::GameLicense::macOS) {
+ request.setHeader(QNetworkRequest::UserAgentHeader, "macSQEXAuthor/2.0.0(MacOSX; ja-jp)");
+ } else {
+ request.setHeader(QNetworkRequest::UserAgentHeader, QString("SQEXAuthor/2.0.0(Windows 6.2; ja-jp; %1)").arg(QString(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 LauncherCore::launchGame(const Profile &profile, const LoginAuth &auth)
+{
+ steamApi->setLauncherMode(false);
+
+#ifdef ENABLE_WATCHDOG
+ if (profile.enableWatchdog) {
+ watchdog->launchGame(profile, auth);
+ } else {
+ beginGameExecutable(profile, auth);
+ }
+#else
+ beginGameExecutable(profile, auth);
+#endif
+}
+
+void LauncherCore::beginGameExecutable(const Profile &profile, const LoginAuth &auth)
+{
+ QString gameExectuable;
+ if (profile.directx9Enabled()) {
+ gameExectuable = profile.gamePath() + "/game/ffxiv.exe";
+ } else {
+ gameExectuable = profile.gamePath() + "/game/ffxiv_dx11.exe";
+ }
+
+ if (profile.dalamudEnabled()) {
+ beginDalamudGame(gameExectuable, profile, auth);
+ } else {
+ beginVanillaGame(gameExectuable, profile, auth);
+ }
+
+ successfulLaunch();
+}
+
+void LauncherCore::beginVanillaGame(const QString &gameExecutablePath, const Profile &profile, const LoginAuth &auth)
+{
+ auto gameProcess = new QProcess();
+ gameProcess->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
+
+ auto args = getGameArgs(profile, auth);
+
+ launchExecutable(profile, gameProcess, {gameExecutablePath, args}, true, true);
+}
+
+void LauncherCore::beginDalamudGame(const QString &gameExecutablePath, const Profile &profile, const LoginAuth &auth)
+{
+ QString gamePath = gameExecutablePath;
+ gamePath = "Z:" + gamePath.replace('/', '\\');
+
+ QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+ dataDir = "Z:" + dataDir.replace('/', '\\');
+
+ auto dalamudProcess = new QProcess();
+
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert("DALAMUD_RUNTIME", dataDir + "\\DalamudRuntime");
+
+#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
+ env.insert("XL_WINEONLINUX", "true");
+#endif
+ dalamudProcess->setProcessEnvironment(env);
+
+ auto args = getGameArgs(profile, auth);
+
+ launchExecutable(profile,
+ dalamudProcess,
+ {dataDir + "/Dalamud/" + "Dalamud.Injector.exe",
+ "launch",
+ "-m",
+ "inject",
+ "--game=" + gamePath,
+ "--dalamud-configuration-path=" + dataDir + "\\dalamudConfig.json",
+ "--dalamud-plugin-directory=" + dataDir + "\\installedPlugins",
+ "--dalamud-asset-directory=" + dataDir + "\\DalamudAssets",
+ "--dalamud-client-language=" + QString::number(profile.language()),
+ "--",
+ args},
+ true,
+ true);
+}
+
+QString LauncherCore::getGameArgs(const Profile &profile, const LoginAuth &auth)
+{
+ struct Argument {
+ QString key, value;
+ };
+
+ QList gameArgs;
+ gameArgs.push_back({"DEV.DataPathType", QString::number(1)});
+ gameArgs.push_back({"DEV.UseSqPack", QString::number(1)});
+
+ 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(profile.language())});
+ gameArgs.push_back({"ver", profile.repositories.repositories[0].version});
+
+ if (!auth.lobbyhost.isEmpty()) {
+ gameArgs.push_back({"DEV.GMServerHost", auth.frontierHost});
+ for (int i = 1; i < 9; i++) {
+ gameArgs.push_back({QString("DEV.LobbyHost0%1").arg(QString::number(i)), auth.lobbyhost});
+ gameArgs.push_back({QString("DEV.LobbyPort0%1").arg(QString::number(i)), QString::number(54994)});
+ }
+ }
+
+ if (profile.account()->license() == Account::GameLicense::WindowsSteam) {
+ gameArgs.push_back({"IsSteam", "1"});
+ }
+
+ const QString argFormat = profile.argumentsEncrypted() ? " /%1 =%2" : " %1=%2";
+
+ QString argJoined;
+ for (const auto &arg : gameArgs) {
+ argJoined += argFormat.arg(arg.key, arg.value);
+ }
+
+ return profile.argumentsEncrypted() ? encryptGameArg(argJoined) : argJoined;
+}
+
+void LauncherCore::launchExecutable(const Profile &profile, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup)
+{
+ QList arguments;
+ auto env = QProcessEnvironment::systemEnvironment();
+
+ if (needsRegistrySetup) {
+#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
+ if (profile.account()->license() == Account::GameLicense::macOS) {
+ addRegistryKey(profile, "HKEY_CURRENT_USER\\Software\\Wine", "HideWineExports", "0");
+ } else {
+ addRegistryKey(profile, "HKEY_CURRENT_USER\\Software\\Wine", "HideWineExports", "1");
+ }
+#endif
+ }
+
+#if defined(Q_OS_LINUX)
+ if (isGame) {
+ if (profile.gamescopeEnabled()) {
+ arguments.push_back("gamescope");
+
+ if (profile.gamescopeFullscreen())
+ arguments.push_back("-f");
+
+ if (profile.gamescopeBorderless())
+ arguments.push_back("-b");
+
+ if (profile.gamescopeWidth() > 0)
+ arguments.push_back("-w " + QString::number(profile.gamescopeWidth()));
+
+ if (profile.gamescopeHeight() > 0)
+ arguments.push_back("-h " + QString::number(profile.gamescopeHeight()));
+
+ if (profile.gamescopeRefreshRate() > 0)
+ arguments.push_back("-r " + QString::number(profile.gamescopeRefreshRate()));
+ }
+ }
+#endif
+
+#ifdef ENABLE_GAMEMODE
+ if (isGame && profile.useGamemode) {
+ gamemode_request_start();
+ }
+#endif
+
+#if defined(Q_OS_LINUX)
+ if (profile.esyncEnabled()) {
+ env.insert("WINEESYNC", QString::number(1));
+ env.insert("WINEFSYNC", QString::number(1));
+ env.insert("WINEFSYNC_FUTEX2", QString::number(1));
+ }
+#endif
+
+#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
+ if (m_isSteam) {
+ const QString steamDirectory = QProcessEnvironment::systemEnvironment().value("STEAM_COMPAT_CLIENT_INSTALL_PATH");
+ const QString compatData =
+ QProcessEnvironment::systemEnvironment().value("STEAM_COMPAT_DATA_PATH"); // TODO: do these have to exist on the root steam folder?
+ const QString protonPath = steamDirectory + "/steamapps/common/Proton 7.0";
+
+ // env.insert("PATH", protonPath + "/dist/bin:" + QProcessEnvironment::systemEnvironment().value("PATH"));
+ // env.insert("WINEDLLPATH", protonPath + "/dist/lib64/wine:" + protonPath + "/dist/lib/wine");
+ // env.insert("LD_LIBRARY_PATH", protonPath + "/dist/lib64:" + protonPath + "/dist/lib");
+ // env.insert("WINEPREFIX", compatData + "/pfx");
+ env.insert("STEAM_COMPAT_CLIENT_INSTALL_PATH", steamDirectory);
+ env.insert("STEAM_COMPAT_DATA_PATH", compatData);
+
+ qInfo() << env.toStringList();
+
+ arguments.push_back(protonPath + "/proton");
+ arguments.push_back("run");
+ } else {
+ env.insert("WINEPREFIX", profile.winePrefixPath());
+
+ // XIV on Mac bundle their own Wine install directory, complete with libs etc
+ if (profile.wineType() == Profile::WineType::XIVOnMac) {
+ // TODO: don't hardcode this
+ QString xivLibPath =
+ "/Applications/XIV on Mac.app/Contents/Resources/wine/lib:/Applications/XIV on "
+ "Mac.app/Contents/Resources/MoltenVK/modern";
+
+ env.insert("DYLD_FALLBACK_LIBRARY_PATH", xivLibPath);
+ env.insert("DYLD_VERSIONED_LIBRARY_PATH", xivLibPath);
+ env.insert("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1");
+ env.insert("MVK_CONFIG_RESUME_LOST_DEVICE", "1");
+ env.insert("MVK_ALLOW_METAL_FENCES", "1");
+ env.insert("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
+ }
+
+#if defined(FLATPAK)
+ arguments.push_back("flatpak-spawn");
+ arguments.push_back("--host");
+#endif
+ arguments.push_back(profile.winePath());
+ }
+#endif
+
+ arguments.append(args);
+
+ qInfo() << arguments;
+
+ auto executable = arguments[0];
+ arguments.removeFirst();
+
+ if (isGame)
+ process->setWorkingDirectory(profile.gamePath() + "/game/");
+
+ process->setProcessEnvironment(env);
+
+ process->start(executable, arguments);
+}
+
+void LauncherCore::readInitialInformation()
+{
+ gamescopeAvailable = checkIfInPath("gamescope");
+ gamemodeAvailable = checkIfInPath("gamemoderun");
+
+ m_profileManager->load();
+ m_accountManager->load();
+
+ // restore profile -> account connections
+ for (auto profile : m_profileManager->profiles()) {
+ if (auto account = m_accountManager->getByUuid(profile->accountUuid())) {
+ profile->setAccount(account);
+ }
+ }
+
+ m_loadingFinished = true;
+ Q_EMIT loadingFinished();
+}
+
+LauncherCore::LauncherCore(bool isSteam)
+ : m_isSteam(isSteam)
+{
+ mgr = new QNetworkAccessManager();
+ sapphireLauncher = new SapphireLauncher(*this);
+ squareLauncher = new SquareLauncher(*this);
+ squareBoot = new SquareBoot(*this, *squareLauncher);
+ // assetUpdater = new AssetUpdater(*this);
+ steamApi = new SteamAPI(*this);
+ m_profileManager = new ProfileManager(*this);
+ m_accountManager = new AccountManager(*this);
+
+#ifdef ENABLE_WATCHDOG
+ watchdog = new Watchdog(*this);
+#endif
+
+ readInitialInformation();
+
+ steamApi->setLauncherMode(true);
+}
+
+bool LauncherCore::checkIfInPath(const QString &program)
+{
+ // TODO: also check /usr/local/bin, /bin32 etc (basically read $PATH)
+ const QString directory = "/usr/bin";
+
+ QFileInfo fileInfo(directory + "/" + program);
+
+ return fileInfo.exists() && fileInfo.isFile();
+}
+
+void LauncherCore::addRegistryKey(const Profile &settings, QString key, QString value, QString data)
+{
+ auto process = new QProcess(this);
+ process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
+ launchExecutable(settings, process, {"reg", "add", std::move(key), "/v", std::move(value), "/d", std::move(data), "/f"}, false, false);
+}
+
+void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword)
+{
+ auto loginInformation = new LoginInformation();
+ loginInformation->profile = profile;
+ loginInformation->username = username;
+ loginInformation->password = password;
+ loginInformation->oneTimePassword = oneTimePassword;
+
+ if (profile->account()->rememberPassword()) {
+ profile->account()->setPassword(password);
+ }
+
+ if (loginInformation->profile->account()->isSapphire()) {
+ sapphireLauncher->login(loginInformation->profile->account()->lobbyUrl(), *loginInformation);
+ } else {
+ squareBoot->checkGateStatus(loginInformation);
+ }
+}
+
+bool LauncherCore::autoLogin(Profile &profile)
+{
+ // TODO: when login fails, we need some way to propagate this back? or not?
+ login(&profile, profile.account()->name(), profile.account()->getPassword(), profile.account()->getOTP());
+
+ return true;
+}
+
+GameInstaller *LauncherCore::createInstaller(Profile &profile)
+{
+ return new GameInstaller(*this, profile, this);
+}
+
+bool LauncherCore::isLoadingFinished() const
+{
+ return m_loadingFinished;
+}
+
+bool LauncherCore::hasAccount() const
+{
+ return false;
+}
+
+ProfileManager *LauncherCore::profileManager()
+{
+ return m_profileManager;
+}
+
+AccountManager *LauncherCore::accountManager()
+{
+ return m_accountManager;
+}
+
+bool LauncherCore::closeWhenLaunched() const
+{
+ return Config::closeWhenLaunched();
+}
+
+void LauncherCore::setCloseWhenLaunched(const bool value)
+{
+ if (value != Config::closeWhenLaunched()) {
+ Config::setCloseWhenLaunched(value);
+ Config::self()->save();
+ Q_EMIT closeWhenLaunchedChanged();
+ }
+}
+
+bool LauncherCore::showNewsBanners() const
+{
+ return Config::showNewsBanners();
+}
+
+void LauncherCore::setShowNewsBanners(const bool value)
+{
+ if (value != Config::showNewsBanners()) {
+ Config::setShowNewsBanners(value);
+ Config::self()->save();
+ Q_EMIT showNewsBannersChanged();
+ }
+}
+
+bool LauncherCore::showNewsList() const
+{
+ return Config::showNewsList();
+}
+
+void LauncherCore::setShowNewsList(const bool value)
+{
+ if (value != Config::showNewsList()) {
+ Config::setShowNewsList(value);
+ Config::self()->save();
+ Q_EMIT showNewsListChanged();
+ }
+}
+
+void LauncherCore::refreshNews()
+{
+ QUrlQuery query;
+ query.addQueryItem("lang", "en-us");
+ query.addQueryItem("media", "pcapp");
+
+ QUrl url;
+ url.setScheme("https");
+ url.setHost("frontier.ffxiv.com");
+ url.setPath("/news/headline.json");
+ url.setQuery(query);
+
+ auto request = QNetworkRequest(QString("%1&%2").arg(url.toString(), QString::number(QDateTime::currentMSecsSinceEpoch())));
+
+ // TODO: really?
+ buildRequest(*profileManager()->getProfile(0), request);
+
+ request.setRawHeader("Accept", "application/json, text/plain, */*");
+ request.setRawHeader("Origin", "https://launcher.finalfantasyxiv.com");
+ request.setRawHeader("Referer",
+ QString("https://launcher.finalfantasyxiv.com/v600/index.html?rc_lang=%1&time=%2")
+ .arg("en-us", QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd-HH"))
+ .toUtf8());
+
+ auto reply = mgr->get(request);
+ QObject::connect(reply, &QNetworkReply::finished, [this, reply] {
+ auto document = QJsonDocument::fromJson(reply->readAll());
+
+ auto headline = new Headline();
+
+ const auto parseNews = [](QJsonObject object) -> News {
+ News news;
+ news.date = QDateTime::fromString(object["date"].toString(), Qt::DateFormat::ISODate);
+ news.id = object["id"].toString();
+ news.tag = object["tag"].toString();
+ news.title = object["title"].toString();
+
+ if (object["url"].toString().isEmpty()) {
+ news.url = QUrl(QString("https://na.finalfantasyxiv.com/lodestone/news/detail/%1").arg(news.id));
+ } else {
+ news.url = QUrl(object["url"].toString());
+ }
+
+ return news;
+ };
+
+ for (auto bannerObject : document.object()["banner"].toArray()) {
+ auto banner = Banner();
+ banner.link = QUrl(bannerObject.toObject()["link"].toString());
+ banner.bannerImage = QUrl(bannerObject.toObject()["lsb_banner"].toString());
+
+ headline->banners.push_back(banner);
+ }
+
+ for (auto newsObject : document.object()["news"].toArray()) {
+ auto news = parseNews(newsObject.toObject());
+ headline->news.push_back(news);
+ }
+
+ for (auto pinnedObject : document.object()["pinned"].toArray()) {
+ auto pinned = parseNews(pinnedObject.toObject());
+ headline->pinned.push_back(pinned);
+ }
+
+ for (auto pinnedObject : document.object()["topics"].toArray()) {
+ auto pinned = parseNews(pinnedObject.toObject());
+ headline->topics.push_back(pinned);
+ }
+
+ m_headline = headline;
+ Q_EMIT newsChanged();
+ });
+}
+
+Headline *LauncherCore::headline()
+{
+ return m_headline;
+}
+
+bool LauncherCore::isSteam() const
+{
+ return m_isSteam;
+}
+
+void LauncherCore::openOfficialLauncher(Profile *profile)
+{
+ struct Argument {
+ QString key, value;
+ };
+
+ QString executeArg("%1%2%3%4");
+ QDateTime dateTime = QDateTime::currentDateTime();
+ executeArg = executeArg.arg(dateTime.date().month() + 1, 2, 10, QLatin1Char('0'));
+ executeArg = executeArg.arg(dateTime.date().day(), 2, 10, QLatin1Char('0'));
+ executeArg = executeArg.arg(dateTime.time().hour(), 2, 10, QLatin1Char('0'));
+ executeArg = executeArg.arg(dateTime.time().minute(), 2, 10, QLatin1Char('0'));
+
+ QList arguments;
+ arguments.push_back({"ExecuteArg", executeArg});
+
+ // find user path
+ QString userPath;
+
+ // TODO: don't put this here
+ QString searchDir;
+#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
+ searchDir = profile->winePrefixPath() + "/drive_c/users";
+#else
+ searchDir = "C:/Users";
+#endif
+
+ QDirIterator it(searchDir);
+ while (it.hasNext()) {
+ QString dir = it.next();
+ QFileInfo fi(dir);
+ QString fileName = fi.fileName();
+
+ // FIXME: is there no easier way to filter out these in Qt?
+ if (fi.fileName() != "Public" && fi.fileName() != "." && fi.fileName() != "..") {
+ userPath = fileName;
+ }
+ }
+
+ arguments.push_back({"UserPath", QString(R"(C:\Users\%1\Documents\My Games\FINAL FANTASY XIV - A Realm Reborn)").arg(userPath)});
+
+ const QString argFormat = " /%1 =%2";
+
+ QString argJoined;
+ for (auto &arg : arguments) {
+ argJoined += argFormat.arg(arg.key, arg.value.replace(" ", " "));
+ }
+
+ QString finalArg = encryptGameArg(argJoined);
+
+ auto launcherProcess = new QProcess();
+ launchExecutable(*profile, launcherProcess, {profile->gamePath() + "/boot/ffxivlauncher64.exe", finalArg}, false, true);
+}
+
+void LauncherCore::openSystemInfo(Profile *profile)
+{
+ auto sysinfoProcess = new QProcess();
+ launchExecutable(*profile, sysinfoProcess, {profile->gamePath() + "/boot/ffxivsysinfo64.exe"}, false, false);
+}
+
+void LauncherCore::openConfigBackup(Profile *profile)
+{
+ auto configProcess = new QProcess();
+ launchExecutable(*profile, configProcess, {profile->gamePath() + "/boot/ffxivconfig64.exe"}, false, false);
+}
diff --git a/launcher/src/main.cpp b/launcher/src/main.cpp
new file mode 100755
index 0000000..b617aa2
--- /dev/null
+++ b/launcher/src/main.cpp
@@ -0,0 +1,85 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "gameinstaller.h"
+#include "launchercore.h"
+#include "sapphirelauncher.h"
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+
+ QApplication app(argc, argv);
+ // Default to org.kde.desktop style unless the user forces another style
+ if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
+ QQuickStyle::setStyle(QStringLiteral("org.kde.desktop"));
+ }
+
+ KLocalizedString::setApplicationDomain("astra");
+ QCoreApplication::setOrganizationDomain("xiv.zone");
+
+ KAboutData about(QStringLiteral("astra"), i18n("Astra"), "0.5.0", i18n("FFXIV Launcher"), KAboutLicense::GPL_V3, i18n("© 2023 Joshua Goins"));
+ about.addAuthor(i18n("Joshua Goins"), i18n("Maintainer"), QStringLiteral("josh@redstrate.com"));
+ about.setHomepage("https://xiv.zone/astra");
+ about.addComponent("physis");
+ about.setDesktopFileName("com.redstrate.astra.desktop"); // TODO: temporary
+
+ KAboutData::setApplicationData(about);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription(i18n("Linux FFXIV Launcher"));
+
+#ifdef ENABLE_STEAM
+ QCommandLineOption steamOption("steam", "Used for booting the launcher from Steam.", "verb");
+ steamOption.setFlags(QCommandLineOption::HiddenFromHelp);
+ parser.addOption(steamOption);
+#endif
+
+ about.setupCommandLine(&parser);
+ parser.parse(QCoreApplication::arguments());
+ about.processCommandLine(&parser);
+
+#ifdef ENABLE_STEAM
+ if (parser.isSet(steamOption)) {
+ const QStringList args = parser.positionalArguments();
+ // Steam tries to use as a compatibiltiy tool, running install scripts (like DirectX), so try to ignore it.
+ if (!args[0].contains("ffxivboot.exe")) {
+ return 0;
+ }
+ }
+
+ LauncherCore c(parser.isSet(steamOption));
+#else
+ LauncherCore c(false);
+#endif
+
+ qmlRegisterSingletonInstance("com.redstrate.astra", 1, 0, "LauncherCore", &c);
+ qmlRegisterUncreatableType("com.redstrate.astra", 1, 0, "GameInstaller", QStringLiteral("Use LauncherCore::createInstaller"));
+ qmlRegisterUncreatableType("com.redstrate.astra", 1, 0, "AccountManager", QStringLiteral("Use LauncherCore::accountManager"));
+ qmlRegisterUncreatableType("com.redstrate.astra", 1, 0, "ProfileManager", QStringLiteral("Use LauncherCore::profileManager"));
+ qmlRegisterUncreatableType("com.redstrate.astra", 1, 0, "Profile", QStringLiteral("Use from ProfileManager"));
+ qmlRegisterUncreatableType("com.redstrate.astra", 1, 0, "Account", QStringLiteral("Use from AccountManager"));
+ qmlRegisterSingletonType("com.redstrate.astra", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
+ return engine->toScriptValue(KAboutData::applicationData());
+ });
+ qmlRegisterUncreatableType("com.redstrate.astra", 1, 0, "Headline", QStringLiteral("Use from AccountManager"));
+ qRegisterMetaType("Banner");
+ qRegisterMetaType