diff --git a/.gitignore b/.gitignore index 5cac679..c9eeabb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.kdev4 .idea/ .DS_Store +.directory diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..db68362 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/libxiv"] + path = external/libxiv + url = ../libxiv.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d8a6667..47c6711 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,13 +33,11 @@ set(SRC include/launcherwindow.h src/gamescopesettingswindow.cpp include/gamescopesettingswindow.h - src/fiinparser.cpp - include/fiinparser.h include/headline.h src/headline.cpp - include/indexparser.h - src/indexparser.cpp - include/config.h) + include/config.h + include/gameinstaller.h + src/gameinstaller.cpp) include(FetchContent) @@ -118,7 +116,7 @@ endif() add_executable(astra ${SRC}) -target_link_libraries(astra PUBLIC ${LIBRARIES}) +target_link_libraries(astra PUBLIC ${LIBRARIES} libxiv) target_include_directories(astra PUBLIC diff --git a/README.md b/README.md index 823c33f..2891f9d 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ have more questions, I suggest reading the [FAQ](https://man.sr.ht/~redstrate/as ![screenshot](misc/screenshot.webp?raw=true) ## Features +* Can **bootstrap a new FFXIV installation** if it can't find one. You can skip the installer entirely! * Can use **native (Windows)** and **Wine-based (macOS, Linux)** versions of FFXIV. * You can use **Dalamud**, which is downloaded within the launcher just like XIVQuickLauncher. * Can connect to the **official Square Enix servers** _as well_ as **Sapphire servers**. diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index c6f96e6..6b2d568 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,37 +1 @@ -include(FetchContent) - -if(NOT TARGET Qt5Keychain::Qt5Keychain) - message("Using built-in qt keychain") - - FetchContent_Declare( - qtkeychain - GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git - GIT_TAG v0.12.0 - ) - - set(BUILD_WITH_QT6 OFF CACHE BOOL "" FORCE) - set(QTKEYCHAIN_STATIC ON CACHE BOOL "" FORCE) - set(BUILD_TRANSLATIONS OFF CACHE BOOL "" FORCE) - - FetchContent_MakeAvailable(qtkeychain) - - set(LIBRARIES qt5keychain ${LIBRARIES} PARENT_SCOPE) - set(KEYCHAIN_INCLUDE_DIRS - ${CMAKE_BINARY_DIR}/_deps/qtkeychain-src - ${CMAKE_BINARY_DIR}/_deps/qtkeychain-build PARENT_SCOPE) -endif() - - -if(NOT TARGET QuaZip::QuaZip) - message("Using built-in quazip") - - FetchContent_Declare( - quazip - GIT_REPOSITORY https://github.com/stachenov/quazip.git - GIT_TAG v1.2 - ) - FetchContent_MakeAvailable(quazip) - - set(LIBRARIES QuaZip ${LIBRARIES} PARENT_SCOPE) - set(QUAZIP_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/_deps/quazip-src/quazip PARENT_SCOPE) -endif() +add_subdirectory(libxiv) diff --git a/external/libxiv b/external/libxiv new file mode 160000 index 0000000..ffb3350 --- /dev/null +++ b/external/libxiv @@ -0,0 +1 @@ +Subproject commit ffb3350ddc65e8248173716e84e25c7fc5aad271 diff --git a/include/fiinparser.h b/include/fiinparser.h deleted file mode 100644 index 8e9a939..0000000 --- a/include/fiinparser.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include -#include - -// this is methods dedicated to parsing "fiin" files, commonly shown as "fileinfo.fiin" - -// header is 1024 bytes -// for some reason, they store unknown1 and unknown 2 in this weird format, -// unknown1 is capped at 256 (in decimal) and will overflow into unknown 2 -// for example, 1 is equal to unknown1 = 96 and unknown2 = 0 -// 96 / 1 == 1 -// if you have say, 14 entries, then unknown1 = 64 and unknown2 = 5 -// 5 (unknown2) * 256 = 1280 + 64 (unknown1) = 1344 -// 1344 / 96 = 14 -// i could've made a mistake and this is actually really common but i don't know -struct FileInfoHeader { - char magic[9]; - uint8_t dummy1[16]; - uint8_t unknown; // version? always seems to be 4 - uint8_t dummy2[2]; - uint8_t unknown1; - uint8_t unknown2; - uint8_t dummy[994]; -}; - -// each entry is 96 bytes -struct FileInfoEntry { - uint8_t dummy[8]; // length of file name in some format - char str[64]; // simple \0 encoded string - uint8_t dummy2[24]; // sha1 -}; - -struct FileInfo { - FileInfoHeader header; - std::vector entries; -}; - -FileInfo readFileInfo(const std::string_view path); \ No newline at end of file diff --git a/include/gameinstaller.h b/include/gameinstaller.h new file mode 100644 index 0000000..7329b7a --- /dev/null +++ b/include/gameinstaller.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +class LauncherCore; + +// TODO: convert to a nice signal/slots class like assetupdater +void installGame(LauncherCore& launcher, std::function returnFunc); \ No newline at end of file diff --git a/include/indexparser.h b/include/indexparser.h deleted file mode 100644 index 6c506e5..0000000 --- a/include/indexparser.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include -#include - -// these are methods dedicated to reading ".index" and ".index2" files -// major thanks to xiv.dev for providing the struct definitions - -enum PlatformId : uint8_t -{ - Win32, - PS3, - PS4 -}; - -// https://github.com/SapphireServer/Sapphire/blob/develop/deps/datReader/SqPack.cpp#L5 -struct SqPackHeader -{ - char magic[0x8]; - PlatformId platformId; - uint8_t padding0[3]; - uint32_t size; - uint32_t version; - uint32_t type; -}; - -struct SqPackIndexHeader -{ - uint32_t size; - uint32_t type; - uint32_t indexDataOffset; - uint32_t indexDataSize; -}; - -struct IndexHashTableEntry -{ - uint64_t hash; - uint32_t unknown : 1; - uint32_t dataFileId : 3; - uint32_t offset : 28; - uint32_t _padding; -}; - -struct Index2HashTableEntry -{ - uint32_t hash; - uint32_t unknown : 1; - uint32_t dataFileId : 3; - uint32_t offset : 28; -}; - -template -struct IndexFile { - SqPackHeader packHeader; - SqPackIndexHeader indexHeader; - - std::vector entries; -}; - -IndexFile readIndexFile(const std::string_view path); -IndexFile readIndex2File(const std::string_view path); \ No newline at end of file diff --git a/src/fiinparser.cpp b/src/fiinparser.cpp deleted file mode 100644 index 5bbced8..0000000 --- a/src/fiinparser.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "fiinparser.h" - -#include -#include - -FileInfo readFileInfo(const std::string_view path) { - FILE* file = fopen(path.data(), "rb"); - if(!file) { - qInfo() << "Failed to read file info " << path.data(); - return {}; - } - - FileInfo info; - fread(&info.header, sizeof info.header, 1, file); - - char magic[9] = "FileInfo"; - if(strcmp(info.header.magic, magic) != 0) - qInfo() << "Invalid magic for fileinfo."; - else - qInfo() << "Got matching magic:" << info.header.magic; - - qInfo() << "unknown (version?) = " << info.header.unknown; - qInfo() << "unknown1 = " << info.header.unknown1; - qInfo() << "unknown2 = " << info.header.unknown2; - - int overflow = info.header.unknown2; - int extra = overflow * 256; - int first = info.header.unknown1 / 96; - int first2 = extra / 96; - int actualEntries = first + first2 + 1; // is this 1 really needed? lol - - qInfo() << "Guessed number of entries: " << actualEntries; - - int numEntries = actualEntries; - for(int i = 0; i < numEntries; i++) { - FileInfoEntry entry; - fread(&entry, sizeof entry, 1, file); - - info.entries.push_back(entry); - - qDebug() << entry.str; - } - - fclose(file); - - return info; -} \ No newline at end of file diff --git a/src/gameinstaller.cpp b/src/gameinstaller.cpp new file mode 100644 index 0000000..e439150 --- /dev/null +++ b/src/gameinstaller.cpp @@ -0,0 +1,34 @@ +#include "gameinstaller.h" + +#include +#include +#include +#include + +#include "launchercore.h" + +void installGame(LauncherCore& launcher, std::function returnFunc) { + QString installDirectory = launcher.getProfile(launcher.defaultProfileIndex).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); + launcher.connect(reply, &QNetworkReply::finished, [reply, installDirectory, returnFunc] { + QString dataDir = + QStandardPaths::writableLocation(QStandardPaths::TempLocation); + + QFile file(dataDir + "/ffxivsetup.exe"); + file.open(QIODevice::WriteOnly); + file.write(reply->readAll()); + file.close(); + + extractBootstrapFiles((dataDir + "/ffxivsetup.exe").toStdString(), installDirectory.toStdString()); + + qDebug() << "Done installing!"; + + returnFunc(); + }); +} \ No newline at end of file diff --git a/src/indexparser.cpp b/src/indexparser.cpp deleted file mode 100644 index f5095eb..0000000 --- a/src/indexparser.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "indexparser.h" - -#include -#include - -template -void commonParseSqPack(FILE* file, IndexFile index) { - fread(&index.packHeader, sizeof index.packHeader, 1, file); - - // data starts at size - fseek(file, index.packHeader.size, SEEK_SET); - - // read index header - fread(&index.indexHeader, sizeof index.indexHeader, 1, file); - - // version should be 1? - qInfo() << index.packHeader.version; - - fseek(file, index.indexHeader.indexDataOffset, SEEK_SET); - - qInfo() << "size: " << index.indexHeader.indexDataSize; -} - -IndexFile readIndexFile(const std::string_view path) { - FILE* file = fopen(path.data(), "rb"); - if(!file) { - qInfo() << "Failed to read file info " << path.data(); - return {}; - } - - IndexFile index; - commonParseSqPack(file, index); - - for(int i = 0; i < index.indexHeader.indexDataSize; i++) { - IndexHashTableEntry entry; - fread(&entry, sizeof entry, 1, file); - - qInfo() << entry.hash; - qInfo() << entry.dataFileId; - qInfo() << entry.offset; - } - - return index; -} - -IndexFile readIndex2File(const std::string_view path) { - FILE* file = fopen(path.data(), "rb"); - if(!file) { - qInfo() << "Failed to read file info " << path.data(); - return {}; - } - - IndexFile index; - commonParseSqPack(file, index); - - for(int i = 0; i < index.indexHeader.indexDataSize; i++) { - Index2HashTableEntry entry; - fread(&entry, sizeof entry, 1, file); - - qInfo() << entry.hash; - qInfo() << entry.dataFileId; - qInfo() << entry.offset; - } - - return index; -} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 8b8ad9f..37b7cf7 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include "sapphirelauncher.h" #include "squareboot.h" +#include "gameinstaller.h" int main(int argc, char* argv[]) { QApplication app(argc, argv); @@ -86,6 +88,21 @@ int main(int argc, char* argv[]) { } } + if(!QDir(c.getProfile(c.defaultProfileIndex).gamePath).exists()) { + auto messageBox = new QMessageBox(QMessageBox::Information, "No Game Found", "No game was found to be installed yet. Would you like to install FFXIV now?"); + + auto installButton = messageBox->addButton("Install Game", QMessageBox::HelpRole); + c.connect(installButton, &QPushButton::clicked, [&c, messageBox] { + installGame(c, [messageBox] { + messageBox->close(); + }); + }); + + messageBox->addButton(QMessageBox::StandardButton::No); + + messageBox->show(); + } + LauncherWindow w(c); if(!parser.isSet(noguiOption)) { w.show();