Some checks are pending
Build AeThex Engine / build-windows (push) Waiting to run
Build AeThex Engine / build-linux (push) Waiting to run
Build AeThex Engine / build-macos (push) Waiting to run
Build AeThex Engine / create-release (push) Blocked by required conditions
Deploy Docsify Documentation / build (push) Waiting to run
Deploy Docsify Documentation / deploy (push) Blocked by required conditions
692 lines
22 KiB
C++
692 lines
22 KiB
C++
/**************************************************************************/
|
|
/* aethex_launcher.cpp */
|
|
/**************************************************************************/
|
|
/* AeThex Engine */
|
|
/* https://aethex.dev */
|
|
/**************************************************************************/
|
|
|
|
#include "aethex_launcher.h"
|
|
|
|
#include "core/io/json.h"
|
|
#include "core/io/dir_access.h"
|
|
#include "core/io/file_access.h"
|
|
#include "core/io/http_client.h"
|
|
#include "core/os/os.h"
|
|
#include "core/os/time.h"
|
|
#include "core/config/project_settings.h"
|
|
#include "core/crypto/crypto.h"
|
|
|
|
AethexLauncher *AethexLauncher::singleton = nullptr;
|
|
|
|
AethexLauncher *AethexLauncher::get_singleton() {
|
|
return singleton;
|
|
}
|
|
|
|
void AethexLauncher::_bind_methods() {
|
|
// Initialization
|
|
ClassDB::bind_method(D_METHOD("initialize"), &AethexLauncher::initialize);
|
|
ClassDB::bind_method(D_METHOD("shutdown"), &AethexLauncher::shutdown);
|
|
|
|
// API Configuration
|
|
ClassDB::bind_method(D_METHOD("set_api_base_url", "url"), &AethexLauncher::set_api_base_url);
|
|
ClassDB::bind_method(D_METHOD("get_api_base_url"), &AethexLauncher::get_api_base_url);
|
|
ClassDB::bind_method(D_METHOD("set_supabase_config", "url", "anon_key"), &AethexLauncher::set_supabase_config);
|
|
|
|
// Authentication
|
|
ClassDB::bind_method(D_METHOD("sign_in_with_email", "email", "password"), &AethexLauncher::sign_in_with_email);
|
|
ClassDB::bind_method(D_METHOD("sign_up_with_email", "email", "password", "username"), &AethexLauncher::sign_up_with_email);
|
|
ClassDB::bind_method(D_METHOD("sign_in_with_oauth", "provider"), &AethexLauncher::sign_in_with_oauth);
|
|
ClassDB::bind_method(D_METHOD("sign_out"), &AethexLauncher::sign_out);
|
|
ClassDB::bind_method(D_METHOD("is_authenticated"), &AethexLauncher::is_authenticated);
|
|
ClassDB::bind_method(D_METHOD("get_oauth_url", "provider"), &AethexLauncher::get_oauth_url);
|
|
ClassDB::bind_method(D_METHOD("handle_oauth_callback", "code", "provider"), &AethexLauncher::handle_oauth_callback);
|
|
|
|
// User info
|
|
ClassDB::bind_method(D_METHOD("get_user_id"), &AethexLauncher::get_user_id);
|
|
ClassDB::bind_method(D_METHOD("get_username"), &AethexLauncher::get_username);
|
|
ClassDB::bind_method(D_METHOD("get_email"), &AethexLauncher::get_email);
|
|
ClassDB::bind_method(D_METHOD("get_avatar_url"), &AethexLauncher::get_avatar_url);
|
|
|
|
// Sub-systems
|
|
ClassDB::bind_method(D_METHOD("get_game_library"), &AethexLauncher::get_game_library);
|
|
ClassDB::bind_method(D_METHOD("get_download_manager"), &AethexLauncher::get_download_manager);
|
|
ClassDB::bind_method(D_METHOD("get_store"), &AethexLauncher::get_store);
|
|
ClassDB::bind_method(D_METHOD("get_friend_system"), &AethexLauncher::get_friend_system);
|
|
ClassDB::bind_method(D_METHOD("get_current_profile"), &AethexLauncher::get_current_profile);
|
|
|
|
// Profile
|
|
ClassDB::bind_method(D_METHOD("fetch_launcher_profile"), &AethexLauncher::fetch_launcher_profile);
|
|
ClassDB::bind_method(D_METHOD("update_launcher_profile", "data"), &AethexLauncher::update_launcher_profile);
|
|
ClassDB::bind_method(D_METHOD("create_launcher_profile", "gamertag"), &AethexLauncher::create_launcher_profile);
|
|
|
|
// Quick actions
|
|
ClassDB::bind_method(D_METHOD("launch_game", "game_id"), &AethexLauncher::launch_game);
|
|
ClassDB::bind_method(D_METHOD("install_game", "game_id"), &AethexLauncher::install_game);
|
|
ClassDB::bind_method(D_METHOD("uninstall_game", "game_id"), &AethexLauncher::uninstall_game);
|
|
|
|
// Paths
|
|
ClassDB::bind_method(D_METHOD("get_games_directory"), &AethexLauncher::get_games_directory);
|
|
ClassDB::bind_method(D_METHOD("get_downloads_directory"), &AethexLauncher::get_downloads_directory);
|
|
ClassDB::bind_method(D_METHOD("get_cache_directory"), &AethexLauncher::get_cache_directory);
|
|
ClassDB::bind_method(D_METHOD("set_games_directory", "path"), &AethexLauncher::set_games_directory);
|
|
|
|
// Signals
|
|
ADD_SIGNAL(MethodInfo("authenticated", PropertyInfo(Variant::DICTIONARY, "user_data")));
|
|
ADD_SIGNAL(MethodInfo("authentication_failed", PropertyInfo(Variant::STRING, "error")));
|
|
ADD_SIGNAL(MethodInfo("signed_out"));
|
|
ADD_SIGNAL(MethodInfo("profile_updated", PropertyInfo(Variant::DICTIONARY, "profile")));
|
|
ADD_SIGNAL(MethodInfo("profile_created", PropertyInfo(Variant::DICTIONARY, "profile")));
|
|
ADD_SIGNAL(MethodInfo("game_launched", PropertyInfo(Variant::STRING, "game_id")));
|
|
ADD_SIGNAL(MethodInfo("game_installed", PropertyInfo(Variant::STRING, "game_id")));
|
|
ADD_SIGNAL(MethodInfo("game_uninstalled", PropertyInfo(Variant::STRING, "game_id")));
|
|
}
|
|
|
|
AethexLauncher::AethexLauncher() {
|
|
singleton = this;
|
|
|
|
// Initialize sub-systems
|
|
game_library.instantiate();
|
|
download_manager.instantiate();
|
|
store.instantiate();
|
|
friend_system.instantiate();
|
|
current_profile.instantiate();
|
|
|
|
// Default config path
|
|
config_path = OS::get_singleton()->get_user_data_dir() + "/aethex_launcher.cfg";
|
|
}
|
|
|
|
AethexLauncher::~AethexLauncher() {
|
|
shutdown();
|
|
singleton = nullptr;
|
|
}
|
|
|
|
void AethexLauncher::initialize() {
|
|
_load_config();
|
|
_load_cached_auth();
|
|
|
|
// Initialize sub-systems
|
|
if (game_library.is_valid()) {
|
|
game_library->set_launcher(this);
|
|
game_library->load_library();
|
|
}
|
|
|
|
if (download_manager.is_valid()) {
|
|
download_manager->set_launcher(this);
|
|
}
|
|
|
|
if (store.is_valid()) {
|
|
store->set_launcher(this);
|
|
}
|
|
|
|
if (friend_system.is_valid()) {
|
|
friend_system->set_launcher(this);
|
|
}
|
|
}
|
|
|
|
void AethexLauncher::shutdown() {
|
|
_save_config();
|
|
|
|
if (download_manager.is_valid()) {
|
|
download_manager->cancel_all();
|
|
}
|
|
}
|
|
|
|
void AethexLauncher::_load_config() {
|
|
config.instantiate();
|
|
|
|
if (FileAccess::exists(config_path)) {
|
|
config->load(config_path);
|
|
|
|
api_base_url = config->get_value("api", "base_url", "https://api.aethex.dev");
|
|
supabase_url = config->get_value("api", "supabase_url", "");
|
|
supabase_anon_key = config->get_value("api", "supabase_key", "");
|
|
}
|
|
}
|
|
|
|
void AethexLauncher::_save_config() {
|
|
if (config.is_valid()) {
|
|
config->set_value("api", "base_url", api_base_url);
|
|
config->set_value("api", "supabase_url", supabase_url);
|
|
config->set_value("api", "supabase_key", supabase_anon_key);
|
|
|
|
// Save auth if logged in
|
|
if (authenticated) {
|
|
config->set_value("auth", "token", auth_token);
|
|
config->set_value("auth", "user_id", user_id);
|
|
config->set_value("auth", "username", username);
|
|
config->set_value("auth", "email", email);
|
|
} else {
|
|
config->erase_section("auth");
|
|
}
|
|
|
|
config->save(config_path);
|
|
}
|
|
}
|
|
|
|
void AethexLauncher::_load_cached_auth() {
|
|
if (config.is_valid() && config->has_section("auth")) {
|
|
auth_token = config->get_value("auth", "token", "");
|
|
user_id = config->get_value("auth", "user_id", "");
|
|
username = config->get_value("auth", "username", "");
|
|
email = config->get_value("auth", "email", "");
|
|
|
|
if (!auth_token.is_empty()) {
|
|
authenticated = true;
|
|
// Verify token is still valid
|
|
refresh_auth_token();
|
|
}
|
|
}
|
|
}
|
|
|
|
Dictionary AethexLauncher::_make_api_request(const String &p_endpoint, int p_method, const Dictionary &p_data) {
|
|
Dictionary result;
|
|
result["success"] = false;
|
|
|
|
// Use Supabase if configured
|
|
String url = supabase_url.is_empty() ? api_base_url : supabase_url;
|
|
|
|
// This is a simplified sync request - in production, use HTTPRequest node for async
|
|
Ref<HTTPClient> http = Ref<HTTPClient>(HTTPClient::create());
|
|
|
|
Error err = http->connect_to_host(url, 443, TLSOptions::client());
|
|
if (err != OK) {
|
|
result["error"] = "Failed to connect to server";
|
|
return result;
|
|
}
|
|
|
|
// Wait for connection
|
|
while (http->get_status() == HTTPClient::STATUS_CONNECTING ||
|
|
http->get_status() == HTTPClient::STATUS_RESOLVING) {
|
|
http->poll();
|
|
OS::get_singleton()->delay_usec(100000);
|
|
}
|
|
|
|
if (http->get_status() != HTTPClient::STATUS_CONNECTED) {
|
|
result["error"] = "Connection failed";
|
|
return result;
|
|
}
|
|
|
|
// Build headers
|
|
Vector<String> headers;
|
|
headers.push_back("Content-Type: application/json");
|
|
if (!auth_token.is_empty()) {
|
|
headers.push_back("Authorization: Bearer " + auth_token);
|
|
}
|
|
if (!supabase_anon_key.is_empty()) {
|
|
headers.push_back("apikey: " + supabase_anon_key);
|
|
}
|
|
|
|
// Build body
|
|
String body = "";
|
|
if (!p_data.is_empty()) {
|
|
body = JSON::stringify(p_data);
|
|
}
|
|
|
|
// Make request
|
|
CharString body_bytes = body.utf8();
|
|
err = http->request((HTTPClient::Method)p_method, p_endpoint, headers, (const uint8_t *)body_bytes.get_data(), body_bytes.length());
|
|
if (err != OK) {
|
|
result["error"] = "Request failed";
|
|
return result;
|
|
}
|
|
|
|
// Wait for response
|
|
while (http->get_status() == HTTPClient::STATUS_REQUESTING) {
|
|
http->poll();
|
|
OS::get_singleton()->delay_usec(100000);
|
|
}
|
|
|
|
if (!http->has_response()) {
|
|
result["error"] = "No response received";
|
|
return result;
|
|
}
|
|
|
|
// Read response
|
|
PackedByteArray response_body;
|
|
while (http->get_status() == HTTPClient::STATUS_BODY) {
|
|
http->poll();
|
|
PackedByteArray chunk = http->read_response_body_chunk();
|
|
if (chunk.size() > 0) {
|
|
response_body.append_array(chunk);
|
|
}
|
|
}
|
|
|
|
int response_code = http->get_response_code();
|
|
String response_str = String::utf8((const char *)response_body.ptr(), response_body.size());
|
|
|
|
if (response_code >= 200 && response_code < 300) {
|
|
result["success"] = true;
|
|
if (!response_str.is_empty()) {
|
|
Variant parsed = JSON::parse_string(response_str);
|
|
if (parsed.get_type() != Variant::NIL) {
|
|
result["data"] = parsed;
|
|
}
|
|
}
|
|
} else {
|
|
result["error"] = "Request failed with code: " + String::num_int64(response_code);
|
|
if (!response_str.is_empty()) {
|
|
Variant parsed = JSON::parse_string(response_str);
|
|
if (parsed.get_type() == Variant::DICTIONARY) {
|
|
Dictionary err_data = parsed;
|
|
if (err_data.has("message")) {
|
|
result["error"] = err_data["message"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// API Configuration
|
|
void AethexLauncher::set_api_base_url(const String &p_url) {
|
|
api_base_url = p_url;
|
|
}
|
|
|
|
String AethexLauncher::get_api_base_url() const {
|
|
return api_base_url;
|
|
}
|
|
|
|
void AethexLauncher::set_supabase_config(const String &p_url, const String &p_anon_key) {
|
|
supabase_url = p_url;
|
|
supabase_anon_key = p_anon_key;
|
|
}
|
|
|
|
// Authentication
|
|
Error AethexLauncher::sign_in_with_email(const String &p_email, const String &p_password) {
|
|
Dictionary data;
|
|
data["email"] = p_email;
|
|
data["password"] = p_password;
|
|
|
|
String endpoint = supabase_url.is_empty() ? "/api/v1/auth/login" : "/auth/v1/token?grant_type=password";
|
|
|
|
Dictionary response = _make_api_request(endpoint, HTTPClient::METHOD_POST, data);
|
|
|
|
if (response.get("success", false)) {
|
|
Dictionary resp_data = response.get("data", Dictionary());
|
|
|
|
// Handle both our API and Supabase response formats
|
|
if (resp_data.has("accessToken")) {
|
|
auth_token = resp_data["accessToken"];
|
|
} else if (resp_data.has("access_token")) {
|
|
auth_token = resp_data["access_token"];
|
|
} else if (resp_data.has("token")) {
|
|
auth_token = resp_data["token"];
|
|
}
|
|
|
|
// Handle our API's user format
|
|
if (resp_data.has("user")) {
|
|
Dictionary user = resp_data["user"];
|
|
user_id = user.get("id", "");
|
|
email = user.get("email", "");
|
|
username = user.get("username", email.get_slice("@", 0));
|
|
avatar_url = user.get("avatarUrl", "");
|
|
|
|
// Fallback for Supabase metadata format
|
|
if (username.is_empty()) {
|
|
Dictionary meta = user.get("user_metadata", Dictionary());
|
|
username = meta.get("username", email.get_slice("@", 0));
|
|
avatar_url = meta.get("avatar_url", "");
|
|
}
|
|
}
|
|
|
|
authenticated = true;
|
|
_save_config();
|
|
|
|
Dictionary user_data;
|
|
user_data["id"] = user_id;
|
|
user_data["email"] = email;
|
|
user_data["username"] = username;
|
|
user_data["avatar_url"] = avatar_url;
|
|
emit_signal("authenticated", user_data);
|
|
|
|
// Fetch launcher profile
|
|
fetch_launcher_profile();
|
|
|
|
return OK;
|
|
}
|
|
|
|
String error = response.get("error", "Authentication failed");
|
|
emit_signal("authentication_failed", error);
|
|
return ERR_UNAUTHORIZED;
|
|
}
|
|
|
|
Error AethexLauncher::sign_up_with_email(const String &p_email, const String &p_password, const String &p_username) {
|
|
Dictionary data;
|
|
data["email"] = p_email;
|
|
data["password"] = p_password;
|
|
data["username"] = p_username;
|
|
|
|
String endpoint = supabase_url.is_empty() ? "/api/v1/auth/register" : "/auth/v1/signup";
|
|
|
|
// For Supabase, put username in metadata
|
|
if (!supabase_url.is_empty()) {
|
|
Dictionary meta;
|
|
meta["username"] = p_username;
|
|
data["data"] = meta;
|
|
}
|
|
|
|
Dictionary response = _make_api_request(endpoint, HTTPClient::METHOD_POST, data);
|
|
|
|
if (response.get("success", false)) {
|
|
// Auto sign in after registration
|
|
return sign_in_with_email(p_email, p_password);
|
|
}
|
|
|
|
String error = response.get("error", "Registration failed");
|
|
emit_signal("authentication_failed", error);
|
|
return ERR_CANT_CREATE;
|
|
}
|
|
|
|
Error AethexLauncher::sign_in_with_oauth(const String &p_provider) {
|
|
// Get OAuth URL and open in browser
|
|
String url = get_oauth_url(p_provider);
|
|
if (url.is_empty()) {
|
|
emit_signal("authentication_failed", "Invalid OAuth provider");
|
|
return ERR_INVALID_PARAMETER;
|
|
}
|
|
|
|
OS::get_singleton()->shell_open(url);
|
|
return OK;
|
|
}
|
|
|
|
String AethexLauncher::get_oauth_url(const String &p_provider) const {
|
|
if (supabase_url.is_empty()) {
|
|
return "";
|
|
}
|
|
|
|
String redirect_uri = "aethex://auth/callback";
|
|
return supabase_url + "/auth/v1/authorize?provider=" + p_provider + "&redirect_to=" + redirect_uri;
|
|
}
|
|
|
|
void AethexLauncher::handle_oauth_callback(const String &p_code, const String &p_provider) {
|
|
// Exchange code for token
|
|
Dictionary data;
|
|
data["code"] = p_code;
|
|
|
|
String endpoint = "/auth/v1/token?grant_type=authorization_code";
|
|
|
|
Dictionary response = _make_api_request(endpoint, HTTPClient::METHOD_POST, data);
|
|
|
|
if (response.get("success", false)) {
|
|
Dictionary resp_data = response.get("data", Dictionary());
|
|
auth_token = resp_data.get("access_token", "");
|
|
|
|
if (resp_data.has("user")) {
|
|
Dictionary user = resp_data["user"];
|
|
user_id = user.get("id", "");
|
|
email = user.get("email", "");
|
|
|
|
Dictionary meta = user.get("user_metadata", Dictionary());
|
|
username = meta.get("username", email.get_slice("@", 0));
|
|
avatar_url = meta.get("avatar_url", "");
|
|
}
|
|
|
|
authenticated = true;
|
|
_save_config();
|
|
|
|
Dictionary user_data;
|
|
user_data["id"] = user_id;
|
|
user_data["email"] = email;
|
|
user_data["username"] = username;
|
|
user_data["avatar_url"] = avatar_url;
|
|
emit_signal("authenticated", user_data);
|
|
|
|
fetch_launcher_profile();
|
|
} else {
|
|
emit_signal("authentication_failed", response.get("error", "OAuth failed"));
|
|
}
|
|
}
|
|
|
|
void AethexLauncher::sign_out() {
|
|
auth_token = "";
|
|
user_id = "";
|
|
username = "";
|
|
email = "";
|
|
avatar_url = "";
|
|
authenticated = false;
|
|
|
|
if (current_profile.is_valid()) {
|
|
current_profile->clear();
|
|
}
|
|
|
|
_save_config();
|
|
emit_signal("signed_out");
|
|
}
|
|
|
|
bool AethexLauncher::is_authenticated() const {
|
|
return authenticated;
|
|
}
|
|
|
|
void AethexLauncher::refresh_auth_token() {
|
|
if (auth_token.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
// Verify token by fetching user
|
|
Dictionary response = _make_api_request("/auth/v1/user", HTTPClient::METHOD_GET);
|
|
|
|
if (!response.get("success", false)) {
|
|
// Token expired, sign out
|
|
sign_out();
|
|
}
|
|
}
|
|
|
|
// User info
|
|
String AethexLauncher::get_user_id() const {
|
|
return user_id;
|
|
}
|
|
|
|
String AethexLauncher::get_username() const {
|
|
return username;
|
|
}
|
|
|
|
String AethexLauncher::get_email() const {
|
|
return email;
|
|
}
|
|
|
|
String AethexLauncher::get_avatar_url() const {
|
|
return avatar_url;
|
|
}
|
|
|
|
String AethexLauncher::get_auth_token() const {
|
|
return auth_token;
|
|
}
|
|
|
|
// Sub-systems
|
|
Ref<GameLibrary> AethexLauncher::get_game_library() const {
|
|
return game_library;
|
|
}
|
|
|
|
Ref<DownloadManager> AethexLauncher::get_download_manager() const {
|
|
return download_manager;
|
|
}
|
|
|
|
Ref<LauncherStore> AethexLauncher::get_store() const {
|
|
return store;
|
|
}
|
|
|
|
Ref<FriendSystem> AethexLauncher::get_friend_system() const {
|
|
return friend_system;
|
|
}
|
|
|
|
Ref<LauncherProfile> AethexLauncher::get_current_profile() const {
|
|
return current_profile;
|
|
}
|
|
|
|
// Launcher Profile
|
|
Error AethexLauncher::fetch_launcher_profile() {
|
|
if (!authenticated) {
|
|
return ERR_UNAUTHORIZED;
|
|
}
|
|
|
|
String endpoint = "/rest/v1/launcher_profiles?user_id=eq." + user_id + "&select=*";
|
|
|
|
Dictionary response = _make_api_request(endpoint, HTTPClient::METHOD_GET);
|
|
|
|
if (response.get("success", false)) {
|
|
Variant data = response.get("data", Variant());
|
|
if (data.get_type() == Variant::ARRAY) {
|
|
Array profiles = data;
|
|
if (profiles.size() > 0) {
|
|
Dictionary profile = profiles[0];
|
|
if (current_profile.is_valid()) {
|
|
current_profile->from_dictionary(profile);
|
|
}
|
|
emit_signal("profile_updated", profile);
|
|
return OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ERR_DOES_NOT_EXIST;
|
|
}
|
|
|
|
Error AethexLauncher::update_launcher_profile(const Dictionary &p_data) {
|
|
if (!authenticated || !current_profile.is_valid()) {
|
|
return ERR_UNAUTHORIZED;
|
|
}
|
|
|
|
String endpoint = "/rest/v1/launcher_profiles?id=eq." + current_profile->get_id();
|
|
|
|
Dictionary response = _make_api_request(endpoint, HTTPClient::METHOD_PATCH, p_data);
|
|
|
|
if (response.get("success", false)) {
|
|
fetch_launcher_profile();
|
|
return OK;
|
|
}
|
|
|
|
return ERR_CANT_ACQUIRE_RESOURCE;
|
|
}
|
|
|
|
Error AethexLauncher::create_launcher_profile(const String &p_gamertag) {
|
|
if (!authenticated) {
|
|
return ERR_UNAUTHORIZED;
|
|
}
|
|
|
|
Dictionary data;
|
|
data["user_id"] = user_id;
|
|
data["display_name"] = p_gamertag;
|
|
data["status"] = "online";
|
|
|
|
String endpoint = "/rest/v1/launcher_profiles";
|
|
|
|
Dictionary response = _make_api_request(endpoint, HTTPClient::METHOD_POST, data);
|
|
|
|
if (response.get("success", false)) {
|
|
Dictionary profile = response.get("data", Dictionary());
|
|
if (current_profile.is_valid()) {
|
|
current_profile->from_dictionary(profile);
|
|
}
|
|
emit_signal("profile_created", profile);
|
|
return OK;
|
|
}
|
|
|
|
return ERR_CANT_CREATE;
|
|
}
|
|
|
|
// Quick actions
|
|
Error AethexLauncher::launch_game(const String &p_game_id) {
|
|
if (!game_library.is_valid()) {
|
|
return ERR_UNCONFIGURED;
|
|
}
|
|
|
|
Ref<GameEntry> game = game_library->get_game(p_game_id);
|
|
if (!game.is_valid()) {
|
|
return ERR_DOES_NOT_EXIST;
|
|
}
|
|
|
|
String exe_path = game->get_executable_path();
|
|
if (exe_path.is_empty() || !FileAccess::exists(exe_path)) {
|
|
return ERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
List<String> args;
|
|
String output;
|
|
int exit_code;
|
|
|
|
Error err = OS::get_singleton()->execute(exe_path, args, &output, &exit_code, false);
|
|
|
|
if (err == OK) {
|
|
game->set_last_played(Time::get_singleton()->get_datetime_string_from_system());
|
|
game_library->save_library();
|
|
emit_signal("game_launched", p_game_id);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
Error AethexLauncher::install_game(const String &p_game_id) {
|
|
if (!store.is_valid() || !download_manager.is_valid()) {
|
|
return ERR_UNCONFIGURED;
|
|
}
|
|
|
|
Ref<StoreItem> item = store->get_item(p_game_id);
|
|
if (!item.is_valid()) {
|
|
return ERR_DOES_NOT_EXIST;
|
|
}
|
|
|
|
String download_url = item->get_download_url();
|
|
if (download_url.is_empty()) {
|
|
return ERR_INVALID_DATA;
|
|
}
|
|
|
|
String dest_path = get_downloads_directory() + "/" + p_game_id + ".zip";
|
|
|
|
download_manager->start_download(p_game_id, download_url, dest_path);
|
|
|
|
return OK;
|
|
}
|
|
|
|
Error AethexLauncher::uninstall_game(const String &p_game_id) {
|
|
if (!game_library.is_valid()) {
|
|
return ERR_UNCONFIGURED;
|
|
}
|
|
|
|
Ref<GameEntry> game = game_library->get_game(p_game_id);
|
|
if (!game.is_valid()) {
|
|
return ERR_DOES_NOT_EXIST;
|
|
}
|
|
|
|
String install_path = game->get_install_path();
|
|
if (install_path.is_empty()) {
|
|
return ERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
// Remove directory
|
|
Ref<DirAccess> dir = DirAccess::open(install_path.get_base_dir());
|
|
if (dir.is_valid()) {
|
|
dir->remove(install_path);
|
|
}
|
|
|
|
game->set_status(GameEntry::STATUS_NOT_INSTALLED);
|
|
game->set_install_path("");
|
|
game_library->save_library();
|
|
|
|
emit_signal("game_uninstalled", p_game_id);
|
|
|
|
return OK;
|
|
}
|
|
|
|
// Paths
|
|
String AethexLauncher::get_games_directory() const {
|
|
if (config.is_valid() && config->has_section_key("paths", "games")) {
|
|
return config->get_value("paths", "games", "");
|
|
}
|
|
return OS::get_singleton()->get_user_data_dir() + "/games";
|
|
}
|
|
|
|
String AethexLauncher::get_downloads_directory() const {
|
|
return OS::get_singleton()->get_user_data_dir() + "/downloads";
|
|
}
|
|
|
|
String AethexLauncher::get_cache_directory() const {
|
|
return OS::get_singleton()->get_user_data_dir() + "/cache";
|
|
}
|
|
|
|
void AethexLauncher::set_games_directory(const String &p_path) {
|
|
if (config.is_valid()) {
|
|
config->set_value("paths", "games", p_path);
|
|
_save_config();
|
|
}
|
|
}
|