forked from aya/aya
Initial commit
This commit is contained in:
29
client/bootstrapper/CMakeLists.txt
Normal file
29
client/bootstrapper/CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
add_executable(Bootstrapper
|
||||
src/main.cpp
|
||||
resources/qt.qrc
|
||||
|
||||
src/Bootstrapper.cpp
|
||||
src/Bootstrapper.hpp
|
||||
|
||||
${CLIENT_DIR}/common/AppSettings.cpp
|
||||
${CLIENT_DIR}/common/AppSettings.hpp
|
||||
)
|
||||
|
||||
if(AYA_OS_WINDOWS)
|
||||
target_sources(Bootstrapper PRIVATE
|
||||
resources/winrc.h
|
||||
resources/script.rc
|
||||
)
|
||||
|
||||
set_target_properties(Bootstrapper PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
windeployqt(Bootstrapper)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(Bootstrapper PRIVATE SKIP_APP_SETTINGS_LOADING)
|
||||
target_link_libraries(Bootstrapper ${OPENSSL_CRYPTO_LIBRARIES})
|
||||
target_include_directories(Bootstrapper PRIVATE src resources)
|
||||
set_target_properties(Bootstrapper PROPERTIES OUTPUT_NAME "Aya")
|
||||
BIN
client/bootstrapper/resources/icon.ico
Normal file
BIN
client/bootstrapper/resources/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
5
client/bootstrapper/resources/qt.qrc
Normal file
5
client/bootstrapper/resources/qt.qrc
Normal file
@@ -0,0 +1,5 @@
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file>icon.ico</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
49
client/bootstrapper/resources/script.rc
Normal file
49
client/bootstrapper/resources/script.rc
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "winrc.h"
|
||||
|
||||
#if defined(__MINGW64__) || defined(__MINGW32__)
|
||||
// MinGW-w64, MinGW
|
||||
#if defined(__has_include) && __has_include(<winres.h>)
|
||||
#include <winres.h>
|
||||
#else
|
||||
#include <afxres.h>
|
||||
#include <winresrc.h>
|
||||
#endif
|
||||
#else
|
||||
// MSVC, Windows SDK
|
||||
#include <winres.h>
|
||||
#endif
|
||||
|
||||
IDI_ICON1 ICON APP_ICON
|
||||
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_RESOURCE
|
||||
PRODUCTVERSION VERSION_RESOURCE
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x4L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", APP_ORGANIZATION
|
||||
VALUE "FileDescription", APP_DESCRIPTION
|
||||
VALUE "FileVersion", VERSION_RESOURCE_STR
|
||||
VALUE "LegalCopyright", APP_COPYRIGHT
|
||||
VALUE "ProductName", APP_NAME
|
||||
VALUE "ProductVersion", VERSION_RESOURCE_STR
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", PRODUCT_LANGUAGE, PRODUCT_CHARSET
|
||||
END
|
||||
END
|
||||
24
client/bootstrapper/resources/winrc.h
Normal file
24
client/bootstrapper/resources/winrc.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR_MINOR_STR AYA_VERSION_MAJOR_STR "." AYA_VERSION_MINOR_STR
|
||||
#define VERSION_MAJOR_MINOR_PATCH_STR VERSION_MAJOR_MINOR_STR "." AYA_VERSION_PATCH_STR
|
||||
#ifdef AYA_VERSION_TYPE
|
||||
#define VERSION_FULL_STR VERSION_MAJOR_MINOR_PATCH_STR "-" AYA_VERSION_TYPE
|
||||
#else
|
||||
#define VERSION_FULL_STR VERSION_MAJOR_MINOR_PATCH_STR
|
||||
#endif
|
||||
#define VERSION_RESOURCE AYA_VERSION_MAJOR, AYA_VERSION_MINOR, AYA_VERSION_PATCH, 0
|
||||
#define VERSION_RESOURCE_STR VERSION_FULL_STR "\0"
|
||||
|
||||
/*
|
||||
* These properties are part of VarFileInfo.
|
||||
* For more information, please see: https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo-block
|
||||
*/
|
||||
#define PRODUCT_LANGUAGE 0x0409 // en-US
|
||||
#define PRODUCT_CHARSET 1200 // Unicode
|
||||
|
||||
#define APP_ICON "icon.ico"
|
||||
#define APP_NAME AYA_PROJECT_NAME "\0"
|
||||
#define APP_DESCRIPTION AYA_PROJECT_NAME " Bootstrapper\0"
|
||||
#define APP_ORGANIZATION AYA_PROJECT_NAME "\0"
|
||||
#define APP_COPYRIGHT AYA_PROJECT_NAME " License\0"
|
||||
308
client/bootstrapper/src/Bootstrapper.cpp
Normal file
308
client/bootstrapper/src/Bootstrapper.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include "Bootstrapper.hpp"
|
||||
#include <curl/curl.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <rapidjson/writer.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
|
||||
size_t WriteToString(void* contents, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
size_t total = size * nmemb;
|
||||
auto* out = static_cast<std::string*>(userp);
|
||||
out->append(static_cast<const char*>(contents), total);
|
||||
return total;
|
||||
}
|
||||
|
||||
size_t WriteToOStream(void* contents, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
size_t total = size * nmemb;
|
||||
auto* os = static_cast<std::ostream*>(userp);
|
||||
os->write(static_cast<const char*>(contents), static_cast<std::streamsize>(total));
|
||||
return total;
|
||||
}
|
||||
|
||||
Bootstrapper::Bootstrapper(const std::string& mode, bool showUI, bool forceSkipUpdates, bool isUsingInstance, const std::string& instanceUrl,
|
||||
const std::string& instanceAccessKey)
|
||||
: mode(mode)
|
||||
, showUI(showUI)
|
||||
, forceSkipUpdates(forceSkipUpdates)
|
||||
, isUsingInstance(isUsingInstance)
|
||||
, instanceUrl(instanceUrl)
|
||||
, instanceAccessKey(instanceAccessKey)
|
||||
{
|
||||
}
|
||||
std::string Bootstrapper::httpGet(const std::string& path)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl)
|
||||
throw std::runtime_error("CURL init failed for " + path);
|
||||
|
||||
std::string response;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, (this->instanceUrl + path).c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToString);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
|
||||
struct curl_slist* headers = nullptr;
|
||||
if (!this->instanceAccessKey.empty())
|
||||
{
|
||||
std::string authHeader = "Authorization: " + this->instanceAccessKey;
|
||||
headers = curl_slist_append(headers, authHeader.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
if (headers)
|
||||
curl_slist_free_all(headers);
|
||||
|
||||
if (res != CURLE_OK)
|
||||
{
|
||||
std::string msg = "CURL GET failed: ";
|
||||
msg += curl_easy_strerror(res);
|
||||
curl_easy_cleanup(curl);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
return response;
|
||||
}
|
||||
|
||||
int Bootstrapper::downloadFile(const std::string& path, const std::string& outputPath)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
if (!curl)
|
||||
throw std::runtime_error("CURL init failed for " + path);
|
||||
|
||||
std::ofstream ofs(outputPath, std::ios::binary);
|
||||
if (!ofs)
|
||||
{
|
||||
curl_easy_cleanup(curl);
|
||||
throw std::runtime_error("Failed to open output file: " + outputPath);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, (this->instanceUrl + path).c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToOStream);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(&ofs));
|
||||
|
||||
struct curl_slist* headers = nullptr;
|
||||
if (!this->instanceAccessKey.empty())
|
||||
{
|
||||
std::string authHeader = "Authorization: " + this->instanceAccessKey;
|
||||
headers = curl_slist_append(headers, authHeader.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
curl_off_t downloadSize = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloadSize);
|
||||
|
||||
if (headers)
|
||||
curl_slist_free_all(headers);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK)
|
||||
throw std::runtime_error("CURL download failed: " + std::string(curl_easy_strerror(res)));
|
||||
|
||||
return static_cast<int>(downloadSize);
|
||||
}
|
||||
|
||||
bool Bootstrapper::verifySHA256(const std::string& filePath, const std::string& expectedHex)
|
||||
{
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file)
|
||||
throw std::runtime_error("Failed to open file for SHA256: " + filePath);
|
||||
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
|
||||
std::vector<char> buffer(1 << 16);
|
||||
while (file.good())
|
||||
{
|
||||
file.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
std::streamsize r = file.gcount();
|
||||
if (r > 0)
|
||||
SHA256_Update(&ctx, buffer.data(), static_cast<size_t>(r));
|
||||
}
|
||||
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
SHA256_Final(hash, &ctx);
|
||||
|
||||
std::ostringstream oss;
|
||||
for (unsigned char b : hash)
|
||||
{
|
||||
oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
|
||||
}
|
||||
return oss.str() == expectedHex;
|
||||
}
|
||||
|
||||
void Bootstrapper::extractTarZst(const std::string& archivePath, const std::string& outputDir)
|
||||
{
|
||||
struct archive* a = archive_read_new();
|
||||
archive_read_support_format_tar(a);
|
||||
archive_read_support_filter_zstd(a);
|
||||
|
||||
if (archive_read_open_filename(a, archivePath.c_str(), 10240) != ARCHIVE_OK)
|
||||
{
|
||||
std::string err = archive_error_string(a);
|
||||
archive_read_free(a);
|
||||
throw std::runtime_error("Failed to open tar.zst: " + err);
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(outputDir);
|
||||
|
||||
struct archive_entry* entry;
|
||||
while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
|
||||
{
|
||||
const char* pathname = archive_entry_pathname(entry);
|
||||
std::filesystem::path outPath = std::filesystem::path(outputDir) / pathname;
|
||||
|
||||
if (archive_entry_filetype(entry) == AE_IFDIR)
|
||||
{
|
||||
std::filesystem::create_directories(outPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::filesystem::create_directories(outPath.parent_path());
|
||||
std::ofstream ofs(outPath, std::ios::binary);
|
||||
const void* buff;
|
||||
size_t size;
|
||||
la_int64_t offset;
|
||||
while (archive_read_data_block(a, &buff, &size, &offset) == ARCHIVE_OK)
|
||||
{
|
||||
ofs.write(static_cast<const char*>(buff), static_cast<std::streamsize>(size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
archive_read_free(a);
|
||||
}
|
||||
|
||||
rapidjson::Document Bootstrapper::parseJson(const std::string& jsonStr)
|
||||
{
|
||||
rapidjson::Document doc;
|
||||
|
||||
doc.Parse(jsonStr.c_str());
|
||||
if (doc.HasParseError())
|
||||
throw std::runtime_error("Failed to parse JSON");
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
rapidjson::Document Bootstrapper::fetchLatestManifest()
|
||||
{
|
||||
return parseJson(httpGet("api/aya/updater/manifest"));
|
||||
}
|
||||
|
||||
rapidjson::Document Bootstrapper::fetchCachedManifest()
|
||||
{
|
||||
if (std::filesystem::exists("data/manifest.json"))
|
||||
{
|
||||
std::ifstream ifs("data/manifest.json");
|
||||
std::stringstream buffer;
|
||||
buffer << ifs.rdbuf();
|
||||
return parseJson(buffer.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
// return empty data
|
||||
if (!std::filesystem::exists("data"))
|
||||
{
|
||||
std::filesystem::create_directories("data");
|
||||
}
|
||||
|
||||
std::string filePath = "data/manifest.json";
|
||||
std::string emptyData = "{}";
|
||||
std::ofstream ofs(filePath);
|
||||
ofs << emptyData;
|
||||
ofs.close();
|
||||
|
||||
return parseJson(emptyData);
|
||||
}
|
||||
}
|
||||
|
||||
void Bootstrapper::updateCachedManifest(const rapidjson::Document& manifest)
|
||||
{
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
manifest.Accept(writer);
|
||||
|
||||
std::ofstream ofs("data/manifest.json");
|
||||
ofs << buffer.GetString();
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
void Bootstrapper::checkForUpdates()
|
||||
{
|
||||
rapidjson::Document latestManifest = fetchLatestManifest();
|
||||
rapidjson::Document cachedManifest = fetchCachedManifest();
|
||||
|
||||
if (!cachedManifest.HasMember("version") || cachedManifest["version"].GetString() != latestManifest["version"].GetString())
|
||||
{
|
||||
std::string downloadUrl = latestManifest["download_url"].GetString();
|
||||
std::string expectedSha256 = latestManifest["sha256"].GetString();
|
||||
|
||||
std::cout << "New version available: " << latestManifest["version"].GetString() << std::endl;
|
||||
std::cout << "Downloading from: " << downloadUrl << std::endl;
|
||||
|
||||
std::string tempFilePath = "data/update_temp.tar.zst";
|
||||
downloadFile(downloadUrl, tempFilePath);
|
||||
|
||||
if (!verifySHA256(tempFilePath, expectedSha256))
|
||||
{
|
||||
throw std::runtime_error("Downloaded file SHA256 does not match expected value.");
|
||||
}
|
||||
|
||||
extractTarZst(tempFilePath, ".");
|
||||
|
||||
std::filesystem::remove(tempFilePath);
|
||||
|
||||
updateCachedManifest(latestManifest);
|
||||
|
||||
std::cout << "Update applied successfully to version " << latestManifest["version"].GetString() << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "No updates available." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void launchProcess(const std::string& appName, const std::string& commandLine)
|
||||
{
|
||||
std::string fullCommand = appName + " " + commandLine;
|
||||
int result = std::system(fullCommand.c_str());
|
||||
if (result != 0)
|
||||
{
|
||||
throw std::runtime_error("Failed to launch process: " + fullCommand);
|
||||
}
|
||||
}
|
||||
|
||||
void Bootstrapper::start(const std::string& commandLine)
|
||||
{
|
||||
if (isUsingInstance)
|
||||
this->checkForUpdates();
|
||||
|
||||
if (mode == "player")
|
||||
launchProcess("Aya.Player", commandLine);
|
||||
else if (mode == "studio")
|
||||
launchProcess("Aya.Studio", commandLine);
|
||||
else if (mode == "server")
|
||||
launchProcess("Aya.Server", commandLine);
|
||||
}
|
||||
62
client/bootstrapper/src/Bootstrapper.hpp
Normal file
62
client/bootstrapper/src/Bootstrapper.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <rapidjson/document.h>
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QProgressDialog>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class Bootstrapper
|
||||
{
|
||||
private:
|
||||
std::string mode;
|
||||
bool showUI;
|
||||
bool forceSkipUpdates;
|
||||
bool isUsingInstance;
|
||||
std::string instanceUrl;
|
||||
std::string instanceAccessKey;
|
||||
|
||||
std::string httpGet(const std::string& url);
|
||||
int downloadFile(const std::string& url, const std::string& outputPath);
|
||||
|
||||
rapidjson::Document fetchCachedManifest();
|
||||
rapidjson::Document fetchLatestManifest();
|
||||
void updateCachedManifest(const rapidjson::Document& manifest);
|
||||
|
||||
static void extractTarZst(const std::string& archivePath, const std::string& outputDir);
|
||||
static bool verifySHA256(const std::string& filePath, const std::string& expectedHex);
|
||||
static rapidjson::Document parseJson(const std::string& jsonStr);
|
||||
|
||||
public:
|
||||
Bootstrapper(const std::string& mode, bool showUI, bool forceSkipUpdates, bool isUsingInstance, const std::string& instanceUrl,
|
||||
const std::string& instanceAccessKey);
|
||||
|
||||
void update(const rapidjson::Document& manifest);
|
||||
void checkForUpdates();
|
||||
void start(const std::string& commandLine);
|
||||
};
|
||||
|
||||
/*
|
||||
class BootstrapperProgressDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BootstrapperProgressDialog();
|
||||
|
||||
private slots:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onFinished();
|
||||
void onReadyRead();
|
||||
void onError(QNetworkReply::NetworkError code);
|
||||
|
||||
private:
|
||||
QString formatSize(qint64 bytes) const;
|
||||
|
||||
QProgressDialog* progressDialog{nullptr};
|
||||
QNetworkAccessManager* manager{nullptr};
|
||||
QNetworkReply* reply{nullptr};
|
||||
};
|
||||
*/
|
||||
126
client/bootstrapper/src/main.cpp
Normal file
126
client/bootstrapper/src/main.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
// clang-format off
|
||||
|
||||
#include <QApplication>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include "AppSettings.hpp"
|
||||
#include "Bootstrapper.hpp"
|
||||
#include "winrc.h"
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
QCoreApplication* createApplication(int &argc, const char *argv[])
|
||||
{
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!qstrcmp(argv[i], "--no-gui"))
|
||||
return new QCoreApplication(argc, (char**)argv);
|
||||
}
|
||||
|
||||
return new QApplication(argc, (char**)argv);
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
|
||||
bool showUI = qobject_cast<QApplication*>(app.data()) != nullptr;
|
||||
|
||||
QCoreApplication::setOrganizationName(AYA_PROJECT_NAME);
|
||||
QCoreApplication::setApplicationName(AYA_PROJECT_NAME);
|
||||
QCoreApplication::setApplicationVersion(VERSION_FULL_STR);
|
||||
|
||||
po::options_description desc(AYA_PROJECT_NAME " options");
|
||||
|
||||
std::string mode = "player";
|
||||
bool isUsingInstance = false; // we will determine this through our bespoke methods
|
||||
bool forceSkipUpdates = false;
|
||||
|
||||
std::string appSettingsPath;
|
||||
std::string instanceUrl, instanceAccessKey;
|
||||
|
||||
desc.add_options()
|
||||
("help,?", "Usage help")
|
||||
("version,V", "Print version and exit")
|
||||
("player", "Launch player (default)")
|
||||
("studio", "Launch studio")
|
||||
("server", "Launch server")
|
||||
("skip-updates,F", "Skip update check")
|
||||
("no-gui", "Run in no-GUI mode (server only)")
|
||||
("instance-url,U", po::value<std::string>(&instanceUrl), "Instance URL override")
|
||||
("instance-access-key,k", po::value<std::string>(&instanceAccessKey), "Instance access key")
|
||||
("app-settings,S", po::value<std::string>(&appSettingsPath)->default_value("AppSettings.ini"), "Path to AppSettings.ini");
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
|
||||
if (vm.count("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vm.count("version"))
|
||||
{
|
||||
std::cout << VERSION_FULL_STR << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
AppSettings settings(appSettingsPath);
|
||||
if (!settings.load())
|
||||
{
|
||||
std::cout << "Failed to load AppSettings.ini - please make sure it exists with a valid ContentPath under the Aya group, and make sure that it is free of any errors.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (settings.hasGroup("Instance"))
|
||||
{
|
||||
if (settings.has("Instance", "Domain"))
|
||||
if (instanceUrl.empty())
|
||||
instanceUrl = settings.get("Instance", "Domain").value();
|
||||
|
||||
if (settings.has("Instance", "AccessKey"))
|
||||
if (instanceAccessKey.empty())
|
||||
instanceAccessKey = settings.get("Instance", "AccessKey").value();
|
||||
}
|
||||
|
||||
isUsingInstance = !instanceUrl.empty();
|
||||
|
||||
// legacy holdover
|
||||
if (isUsingInstance)
|
||||
if (instanceUrl.rbegin() != instanceUrl.rend() && *instanceUrl.rbegin() != '/')
|
||||
instanceUrl = instanceUrl + "/";
|
||||
|
||||
if (vm.count("studio"))
|
||||
mode = "studio";
|
||||
else if (vm.count("server"))
|
||||
mode = "server";
|
||||
|
||||
if (vm.count("skip-updates"))
|
||||
forceSkipUpdates = true;
|
||||
|
||||
std::vector<std::string> args;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
std::string arg = argv[i];
|
||||
|
||||
// Skip bootstrapper-specific arguments
|
||||
if (arg == "--help" || arg == "-?" ||
|
||||
arg == "--version" || arg == "-V" ||
|
||||
arg == "--player" || arg == "--studio" || arg == "--server" ||
|
||||
arg == "--skip-updates" || arg == "-F")
|
||||
{
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
Bootstrapper bootstrapper(mode, showUI, forceSkipUpdates, isUsingInstance, instanceUrl, instanceAccessKey);
|
||||
bootstrapper.start(boost::algorithm::join(args, " "));
|
||||
|
||||
return app->exec();
|
||||
}
|
||||
Reference in New Issue
Block a user