Open IanTrudel opened 4 years ago
Is there an API? Their freebie game detail page looks somewhat like itch.io.
URLs and all other information are directly in the HTML: https://www.indiegala.com/library/showcase/1
Edit: Incapsula blocks everything.
I would be interested as well especially since they offer Linux versions of games as well.
You could look at this code to see how they are doing things as to how to integrate IndieGala in to GameHub.
This one is actually interesting: https://github.com/burnhamup/galaxy-integration-indiegala/commit/9dad774aa9e4bde16233cd5c320e207cd2960d04
Using the API does circumvent the blocking from incapsula.
Here's a rudimentary non functional diff to the refactoring branch to get someone going:
diff --git a/data/com.github.tkashkin.gamehub.gschema.xml.in b/data/com.github.tkashkin.gamehub.gschema.xml.in
index 667b85f..a84d30a 100644
--- a/data/com.github.tkashkin.gamehub.gschema.xml.in
+++ b/data/com.github.tkashkin.gamehub.gschema.xml.in
@@ -192,6 +192,17 @@
</key>
</schema>
+ <schema path="@SCHEMA_PATH@/auth/indiegala/" id="@SCHEMA_ID@.auth.indiegala">
+ <key name="enabled" type="b">
+ <default>true</default>
+ <summary>Is IndieGala enabled</summary>
+ </key>
+ <key name="authenticated" type="b">
+ <default>false</default>
+ <summary>Is user authenticated</summary>
+ </key>
+ </schema>
+
<schema path="@SCHEMA_PATH@/auth/itch/" id="@SCHEMA_ID@.auth.itch">
<key name="enabled" type="b">
<default>true</default>
@@ -239,6 +250,18 @@
</key>
</schema>
+ <!-- Paths / IndieGala -->
+ <schema path="@SCHEMA_PATH@/paths/indiegala/" id="@SCHEMA_ID@.paths.indiegala">
+ <key name="game-directories" type="as">
+ <default>['~/Games/IndieGala']</default>
+ <summary>IndieGala game directories</summary>
+ </key>
+ <key name="default-game-directory" type="s">
+ <default>'~/Games/IndieGala'</default>
+ <summary>Default IndieGala games directory</summary>
+ </key>
+ </schema>
+
<!-- Paths / itch.io -->
<schema path="@SCHEMA_PATH@/paths/itch/" id="@SCHEMA_ID@.paths.itch">
<key name="home" type="s">
@@ -293,6 +316,17 @@
</key>
</schema>
+ <schema path="@SCHEMA_PATH@/paths/collection/indiegala/" id="@SCHEMA_ID@.paths.collection.indiegala">
+ <key name="game-dir" type="s">
+ <default>'$root/IndieGala/$game'</default>
+ <summary>IndieGala collection: game directory</summary>
+ </key>
+ <key name="installers" type="s">
+ <default>'$game_dir'</default>
+ <summary>IndieGala collection: installers directory</summary>
+ </key>
+ </schema>
+
<!-- Controller -->
<schema path="@SCHEMA_PATH@/controller/" id="@SCHEMA_ID@.controller">
<key name="enabled" type="b">
diff --git a/src/app.vala b/src/app.vala
index 7b0ed93..3dcd818 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -25,6 +25,7 @@ using GameHub.Data.DB;
using GameHub.Data.Sources.Steam;
using GameHub.Data.Sources.GOG;
using GameHub.Data.Sources.Humble;
+using GameHub.Data.Sources.IndieGala;
using GameHub.Data.Sources.Itch;
using GameHub.Data.Sources.User;
using GameHub.Data.Tweaks;
@@ -141,7 +142,7 @@ namespace GameHub
ImageCache.init();
Database.create();
- GameSources = { new Steam(), new GOG(), new Humble(), new Trove(), new Itch(), new User() };
+ GameSources = { new Steam(), new GOG(), new Humble(), new Trove(), new IndieGala(), new Itch(), new User() };
Providers.ImageProviders = { new Providers.Images.Steam(), new Providers.Images.SteamGridDB(), new Providers.Images.JinxSGVI() };
Providers.DataProviders = { new Providers.Data.IGDB() };
diff --git a/src/data/db/tables/Games.vala b/src/data/db/tables/Games.vala
index 38b6c99..79c2acd 100644
--- a/src/data/db/tables/Games.vala
+++ b/src/data/db/tables/Games.vala
@@ -25,6 +25,7 @@ using GameHub.Data.Runnables;
using GameHub.Data.Sources.Steam;
using GameHub.Data.Sources.GOG;
using GameHub.Data.Sources.Humble;
+using GameHub.Data.Sources.IndieGala;
using GameHub.Data.Sources.Itch;
using GameHub.Data.Sources.User;
@@ -351,6 +352,10 @@ namespace GameHub.Data.DB.Tables
{
g = new HumbleGame.from_db((Humble) s, st);
}
+ else if(s is IndieGala)
+ {
+ g = new IndieGalaGame.from_db((IndieGala) s, st);
+ }
else if(s is Itch)
{
g = new ItchGame.from_db((Itch) s, st);
@@ -436,6 +441,10 @@ namespace GameHub.Data.DB.Tables
{
g = new HumbleGame.from_db((Humble) s, st);
}
+ else if(s is IndieGala)
+ {
+ g = new IndieGalaGame.from_db((IndieGala) s, st);
+ }
else if(s is Itch)
{
g = new ItchGame.from_db((Itch) s, st);
diff --git a/src/data/sources/indiegala/IndieGala.vala b/src/data/sources/indiegala/IndieGala.vala
new file mode 100644
index 0000000..9531557
--- /dev/null
+++ b/src/data/sources/indiegala/IndieGala.vala
@@ -0,0 +1,320 @@
+using Gee;
+using Soup;
+
+using GameHub.Data.DB;
+using GameHub.Data.Runnables;
+using GameHub.Utils;
+
+namespace GameHub.Data.Sources.IndieGala
+{
+ public class IndieGala: GameSource
+ {
+ public static IndieGala instance;
+
+ public override string id { get { return "indiegala"; } }
+ public override string name { get { return "IndieGala"; } }
+ public override string icon { get { return "source-gog-symbolic"; } }
+
+ public override bool enabled
+ {
+ get { return settings.enabled; }
+ set { settings.enabled = value; }
+ }
+
+ public string? user_id { get; protected set; }
+ public string? user_name { get; protected set; }
+
+ SList<Soup.Cookie>? cookies = null;
+
+ private Settings.Auth.IndieGala settings;
+
+ public IndieGala()
+ {
+ instance = this;
+
+ settings = Settings.Auth.IndieGala.instance;
+ }
+
+ public override bool is_installed(bool refresh)
+ {
+ return true;
+ }
+
+ public override async bool install()
+ {
+ return true;
+ }
+
+ public override async bool authenticate()
+ {
+ settings.authenticated = true;
+
+ return yield get_cookies();
+ }
+
+ public override bool is_authenticated()
+ {
+ return cookies != null;
+ }
+
+ public override bool can_authenticate_automatically()
+ {
+ return false;
+ }
+
+ private async bool get_cookies()
+ {
+ // if(cookies != null)
+ // {
+ // return true;
+ // }
+
+ var wnd = new GameHub.UI.Windows.WebAuthWindow(this.name, @"https://www.indiegala.com/login", null, "auth");
+
+ wnd.finished.connect(code =>
+ {
+ // user_auth_code = code;
+ // if(GameHub.Application.log_auth)
+ // {
+ // debug("[Auth] IndieGala auth code: %s", code);
+ // }
+ // Idle.add(get_auth_code.callback);
+
+ wnd.webview.web_context.get_cookie_manager().get_cookies.begin(
+ "https://www.indiegala.com",
+ null,
+ (obj, res) => {
+ try
+ {
+ var webview_cookies = wnd.webview.web_context.get_cookie_manager().get_cookies.end(res);
+ cookies = new SList<Soup.Cookie>();
+
+ webview_cookies.foreach(cookie => {
+ cookies.append(cookie);
+ });
+
+ // authenticate_with_exchange_code(authenticate_with_sid(cookies));
+ }
+ catch (Error e) {}
+
+ Idle.add(get_cookies.callback);
+ });
+ });
+
+ wnd.canceled.connect(() => Idle.add(get_cookies.callback));
+
+ wnd.set_size_request(550, 680);
+ wnd.show_all();
+ wnd.present();
+
+ yield;
+
+ return cookies != null;
+ }
+
+ // private async bool get_token()
+ // {
+ // if(user_token != null)
+ // {
+ // return true;
+ // }
+
+ // var url = @"https://auth.gog.com/token?client_id=$(CLIENT_ID)&client_secret=$(CLIENT_SECRET)&grant_type=authorization_code&redirect_uri=$(REDIRECT)&code=$(user_auth_code)";
+ // var root = (yield Parser.parse_remote_json_file_async(url)).get_object();
+ // user_token = root.get_string_member("access_token");
+ // user_refresh_token = root.get_string_member("refresh_token");
+ // user_id = root.get_string_member("user_id");
+
+ // settings.access_token = user_token ?? "";
+ // settings.refresh_token = user_refresh_token ?? "";
+
+ // if(GameHub.Application.log_auth)
+ // {
+ // debug("[Auth] GOG access token: %s", user_token);
+ // debug("[Auth] GOG refresh token: %s", user_refresh_token);
+ // debug("[Auth] GOG user id: %s", user_id);
+ // }
+
+ // return user_token != null;
+ // }
+
+ // public async bool refresh_token()
+ // {
+ // if(user_refresh_token == null)
+ // {
+ // return false;
+ // }
+
+ // if(GameHub.Application.log_auth)
+ // {
+ // debug("[Auth] Refreshing GOG access token with refresh token: %s", user_refresh_token);
+ // }
+
+ // var url = @"https://auth.gog.com/token?client_id=$(CLIENT_ID)&client_secret=$(CLIENT_SECRET)&grant_type=refresh_token&refresh_token=$(user_refresh_token)";
+ // var root_node = yield Parser.parse_remote_json_file_async(url);
+ // var root = root_node != null && root_node.get_node_type() == Json.NodeType.OBJECT ? root_node.get_object() : null;
+
+ // if(root == null)
+ // {
+ // token_needs_refresh = false;
+ // return false;
+ // }
+
+ // user_token = root.get_string_member("access_token");
+ // user_refresh_token = root.get_string_member("refresh_token");
+ // user_id = root.get_string_member("user_id");
+
+ // settings.access_token = user_token ?? "";
+ // settings.refresh_token = user_refresh_token ?? "";
+
+ // if(GameHub.Application.log_auth)
+ // {
+ // debug("[Auth] GOG access token: %s", user_token);
+ // debug("[Auth] GOG refresh token: %s", user_refresh_token);
+ // debug("[Auth] GOG user id: %s", user_id);
+ // }
+
+ // token_needs_refresh = false;
+
+ // return user_token != null;
+ // }
+
+ private ArrayList<Game> _games = new ArrayList<Game>(Game.is_equal);
+
+ public override ArrayList<Game> games { get { return _games; } }
+
+ public override async ArrayList<Game> load_games(Utils.FutureResult2<Game, bool>? game_loaded=null, Utils.Future? cache_loaded=null)
+ {
+ if(_games.size > 0)
+ {
+ return _games;
+ }
+
+ Utils.thread("IndieGalaLoading", () => {
+ _games.clear();
+
+ var cached = Tables.Games.get_all(this);
+ games_count = 0;
+ if(cached.size > 0)
+ {
+ foreach(var g in cached)
+ {
+ if(!Settings.UI.Behavior.instance.merge_games || !Tables.Merges.is_game_merged(g))
+ {
+ _games.add(g);
+ if(game_loaded != null)
+ {
+ game_loaded(g, true);
+ }
+ }
+ games_count++;
+ }
+ }
+
+ if(cache_loaded != null)
+ {
+ cache_loaded();
+ }
+
+ var session = new Session();
+ session.timeout = 5;
+ session.max_conns = 256;
+ session.max_conns_per_host = 256;
+
+ var message = new Message("GET", "https://www.indiegala.com/login_new/user_info");
+ message.request_headers.append("User-Agent", "galaClient");
+ cookies_to_request(cookies, message);
+ var status = session.send_message(message);
+ debug("[Sources.IndieGala.load_games] Status: %s", status.to_string());
+ assert(status <= 400);
+ cookies = cookies_from_response(message);
+
+ var json = Parser.parse_json((string) message.response_body.data);
+ debug(Json.to_string(json, true));
+ assert(json.get_node_type() == Json.NodeType.OBJECT);
+ assert(json.get_object().has_member("status") && json.get_object().get_string_member("status") == "success");
+ assert(json.get_object().has_member("showcase_content"));
+ assert(json.get_object().get_object_member("showcase_content").has_member("status_code_ok") && json.get_object().get_object_member("showcase_content").get_boolean_member("status_code_ok") == true);
+ assert(json.get_object().get_object_member("showcase_content").has_member("content"));
+ assert(json.get_object().get_object_member("showcase_content").get_object_member("content").has_member("user_collection"));
+
+ json.get_object().get_object_member("showcase_content").get_object_member("content").get_array_member("user_collection").foreach_element((array, index, node) =>
+ {
+ var game = new IndieGalaGame(this, node);
+ bool is_new_game = !_games.contains(game);
+
+ if(is_new_game && (!Settings.UI.Behavior.instance.merge_games || !Tables.Merges.is_game_merged(game)))
+ {
+ _games.add(game);
+
+ if(game_loaded != null)
+ {
+ game_loaded(game, false);
+ }
+ }
+
+ if(is_new_game)
+ {
+ games_count++;
+ game.save();
+ }
+ });
+
+ Idle.add(load_games.callback);
+ });
+
+ yield;
+
+ return _games;
+ }
+
+ public override ArrayList<File>? game_dirs
+ {
+ owned get
+ {
+ ArrayList<File>? dirs = null;
+
+ var paths = GameHub.Settings.Paths.IndieGala.instance.game_directories;
+ if(paths != null && paths.length > 0)
+ {
+ foreach(var path in paths)
+ {
+ if(path != null && path.length > 0)
+ {
+ var dir = FS.file(path);
+ if(dir != null)
+ {
+ if(dirs == null) dirs = new ArrayList<File>();
+ dirs.add(dir);
+ }
+ }
+ }
+ }
+
+ return dirs;
+ }
+ }
+
+ public override File? default_game_dir
+ {
+ owned get
+ {
+ var path = GameHub.Settings.Paths.IndieGala.instance.default_game_directory;
+ if(path != null && path.length > 0)
+ {
+ var dir = FS.file(path);
+ if(dir != null && dir.query_exists())
+ {
+ return dir;
+ }
+ }
+ var dirs = game_dirs;
+ if(dirs != null && dirs.size > 0)
+ {
+ return dirs.first();
+ }
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/data/sources/indiegala/IndieGalaGame.vala b/src/data/sources/indiegala/IndieGalaGame.vala
new file mode 100644
index 0000000..ed83441
--- /dev/null
+++ b/src/data/sources/indiegala/IndieGalaGame.vala
@@ -0,0 +1,489 @@
+using Gee;
+
+using GameHub.Data.DB;
+using GameHub.Data.Runnables;
+using GameHub.Data.Runnables.Tasks.Install;
+using GameHub.Data.Tweaks;
+
+using GameHub.Utils;
+using GameHub.Utils.FS;
+
+namespace GameHub.Data.Sources.IndieGala
+{
+ public class IndieGalaGame: Game,
+ Traits.HasActions, Traits.HasExecutableFile, Traits.SupportsCompatTools,
+ Traits.Game.SupportsOverlays, Traits.Game.SupportsTweaks
+ {
+ // Traits.HasActions
+ public override ArrayList<Traits.HasActions.Action>? actions { get; protected set; default = new ArrayList<Traits.HasActions.Action>(); }
+
+ // Traits.HasExecutableFile
+ public override string? executable_path { owned get; set; }
+ public override string? work_dir_path { owned get; set; }
+ public override string? arguments { owned get; set; }
+ public override string? environment { owned get; set; }
+
+ // Traits.SupportsCompatTools
+ public override string? compat_tool { get; set; }
+ public override string? compat_tool_settings { get; set; }
+
+ public bool supports_compat_tools { get { return cast<Traits.SupportsCompatTools>().supports_compat_tools; } }
+ public bool needs_compat { get { return cast<Traits.SupportsCompatTools>().needs_compat; } }
+ public bool force_compat
+ {
+ get { return cast<Traits.SupportsCompatTools>().force_compat; }
+ set { cast<Traits.SupportsCompatTools>().force_compat = value; }
+ }
+ public bool use_compat { get { return cast<Traits.SupportsCompatTools>().use_compat; } }
+
+ // Traits.Game.SupportsOverlays
+ public override ArrayList<Traits.Game.SupportsOverlays.Overlay> overlays { get; set; default = new ArrayList<Traits.Game.SupportsOverlays.Overlay>(); }
+ protected override FSOverlay? fs_overlay { get; set; }
+ protected override string? fs_overlay_last_options { get; set; }
+
+ // Traits.Game.SupportsTweaks
+ public override TweakSet? tweaks { get; set; default = null; }
+
+ public bool has_updates { get; set; default = false; }
+
+ private bool game_info_updating = false;
+ private bool game_info_updated = false;
+
+ private string? prod_slugged_name = null;
+
+ public IndieGalaGame(IndieGala src, Json.Node json_node)
+ {
+ source = src;
+
+ var json_obj = json_node.get_object();
+
+ id = json_obj.get_string_member("prod_id_key_name");
+ name = json_obj.get_string_member("prod_name");
+ icon = "";
+ info = Json.to_string(json_node, false);
+
+ if(json_obj.has_member("prod_dev_image"))
+ {
+ // https://www.indiegalacdn.com/imgs/devs/freebies/products/50025bf2-7190-4a32-aa15-4e4f89ba8da6/prodcover/1615375739.png
+ // image = "https://www.indiegalacdn.com/imgs/devs/"
+ // + json_obj.get_string_member("prod_dev_namespace")
+ // + "/products/"
+ // + json_obj.get_string_member("prod_id_key_name")
+ // + "/prodcover/"
+ // + json_obj.get_string_member("prod_dev_cover");
+
+ // https://www.indiegalacdn.com/imgs/devs/freebies/products/50025bf2-7190-4a32-aa15-4e4f89ba8da6/prodmain/1615375754.png
+ image = "https://www.indiegalacdn.com/imgs/devs/"
+ + json_obj.get_string_member("prod_dev_namespace")
+ + "/products/"
+ + json_obj.get_string_member("prod_id_key_name")
+ + "/prodmain/"
+ + json_obj.get_string_member("prod_dev_image");
+ }
+
+ json_obj.get_array_member("version").foreach_element((array, index, node) => {
+ if(node.get_object().get_int_member("enabled") == 0) return;
+
+ if(node.get_object().get_string_member("os") == "win") platforms.add(Platform.WINDOWS);
+ if(node.get_object().get_string_member("os") == "mac") platforms.add(Platform.MACOS);
+ if(node.get_object().get_string_member("os") == "lin") platforms.add(Platform.LINUX);
+ });
+
+ install_dir = null;
+ executable_path = "${install_dir}/start.sh";
+ work_dir_path = "${install_dir}";
+
+ init_tweaks();
+
+ mount_overlays.begin();
+ update_status();
+ }
+
+ public IndieGalaGame.from_db(IndieGala src, Sqlite.Statement s)
+ {
+ source = src;
+
+ dbinit(s);
+ dbinit_executable(s);
+ dbinit_compat(s);
+ dbinit_tweaks(s);
+
+ mount_overlays.begin();
+ update_status();
+ }
+
+ // public override async void update_game_info()
+ // {
+ // if(game_info_updating) return;
+ // game_info_updating = true;
+
+ // yield remount_overlays();
+ // update_status();
+
+ // if(info_detailed == null || info_detailed.length == 0)
+ // {
+ // var lang = Intl.setlocale(LocaleCategory.ALL, null).down().substring(0, 2);
+ // var url = @"https://api.gog.com/products/$(id)?expand=downloads,description,expanded_dlcs" + (lang != null && lang.length > 0 ? "&locale=" + lang : "");
+
+ // while(true)
+ // {
+ // uint status = 0;
+ // var json = (yield Parser.load_remote_file_async(url, "GET", ((GOG) source).user_token, null, null, out status));
+
+ // if(status == Soup.Status.OK && json != null && json.length > 0)
+ // {
+ // info_detailed = json;
+ // break;
+ // }
+ // else if(status == Soup.Status.UNAUTHORIZED)
+ // {
+ // yield ((GOG) source).refresh_token();
+ // }
+ // else break;
+ // }
+ // }
+
+ // var root = Parser.parse_json(info_detailed);
+
+ // var images = Parser.json_object(root, {"images"});
+ // var desc = Parser.json_object(root, {"description"});
+ // var links = Parser.json_object(root, {"links"});
+
+ // if(image == null || image == "")
+ // {
+ // var i = Parser.parse_json(info).get_object();
+ // image = "https:" + i.get_string_member("image") + "_392.jpg";
+ // }
+
+ // if((icon == null || icon == "") && (images != null && images.has_member("icon")))
+ // {
+ // icon = images.get_string_member("icon");
+ // if(icon != null) icon = "https:" + icon;
+ // else icon = image;
+ // }
+
+ // is_installable = root != null && root.get_node_type() == Json.NodeType.OBJECT
+ // && root.get_object().has_member("is_installable") && root.get_object().get_boolean_member("is_installable");
+
+ // if(desc != null)
+ // {
+ // description = desc.get_string_member("full");
+ // var cool = desc.get_string_member("whats_cool_about_it");
+ // if(cool != null && cool.length > 0)
+ // {
+ // description += "<ul>";
+ // var cool_parts = cool.split("\n");
+ // foreach(var part in cool_parts)
+ // {
+ // part = part.strip();
+ // if(part.length > 0)
+ // {
+ // description += "<li>" + part + "</li>";
+ // }
+ // }
+ // description += "</ul>";
+ // }
+ // }
+
+ // if(links != null)
+ // {
+ // store_page = links.get_string_member("product_card");
+ // }
+
+ // var downloads = Parser.json_object(root, {"downloads"});
+
+ // var installers_json = downloads == null || !downloads.has_member("installers") ? null : downloads.get_array_member("installers");
+ // if(installers_json != null && (installers == null || installers.size == 0))
+ // {
+ // installers = new ArrayList<Runnables.Tasks.Install.Installer>();
+ // foreach(var installer_json in installers_json.get_elements())
+ // {
+ // var installer = new Installer(this, installer_json.get_object());
+ // installers.add(installer);
+ // }
+ // }
+
+ // if(installers.size == 0)
+ // {
+ // is_installable = false;
+ // }
+
+ // var bonuses_json = downloads == null || !downloads.has_member("bonus_content") ? null : downloads.get_array_member("bonus_content");
+ // if(bonuses_json != null && (bonus_content == null || bonus_content.size == 0))
+ // {
+ // bonus_content = new ArrayList<BonusContent>();
+ // Json.Object? bonus_map = null;
+
+ // if(bonus_content_dir != null && bonus_content_dir.query_exists())
+ // {
+ // var map_file = bonus_content_dir.get_child(BonusContent.FILEMAP_NAME);
+ // if(map_file != null && map_file.query_exists())
+ // {
+ // var map_root_node = Parser.parse_json_file(map_file.get_path());
+ // bonus_map = map_root_node != null && map_root_node.get_node_type() == Json.NodeType.OBJECT ? map_root_node.get_object() : null;
+ // }
+ // }
+
+ // foreach(var bonus_json in bonuses_json.get_elements())
+ // {
+ // bonus_content.add(new BonusContent(this, bonus_json.get_object(), bonus_map));
+ // }
+ // }
+
+ // var dlcs_json = root == null || root.get_node_type() != Json.NodeType.OBJECT || !root.get_object().has_member("expanded_dlcs") ? null : root.get_object().get_array_member("expanded_dlcs");
+ // if(dlcs_json != null && (dlc == null || dlc.size == 0))
+ // {
+ // dlc = new ArrayList<DLC>();
+ // foreach(var dlc_json in dlcs_json.get_elements())
+ // {
+ // var d = new DLC(this, dlc_json);
+ // dlc.add(d);
+ // yield d.update_downloads_info();
+ // }
+ // }
+
+ // root = Parser.parse_json(info);
+
+ // var tags_json = root == null || root.get_node_type() != Json.NodeType.OBJECT || !root.get_object().has_member("tags") ? null : root.get_object().get_array_member("tags");
+
+ // if(tags_json != null)
+ // {
+ // foreach(var tag_json in tags_json.get_elements())
+ // {
+ // var tid = source.id + ":" + tag_json.get_string();
+ // foreach(var t in Tables.Tags.TAGS)
+ // {
+ // if(tid == t.id)
+ // {
+ // if(!tags.contains(t)) tags.add(t);
+ // break;
+ // }
+ // }
+ // }
+ // }
+
+ // save();
+
+ // update_status();
+
+ // game_info_updated = true;
+ // game_info_updating = false;
+ // }
+
+ public override async ArrayList<Tasks.Install.Installer>? load_installers()
+ {
+ if(installers != null && installers.size > 0) return installers;
+
+ if(info != null && (installers == null || installers.size == 0))
+ {
+ var installers_json = Parser.parse_json(info);
+ installers = new ArrayList<Runnables.Tasks.Install.Installer>();
+ foreach(var platform in platforms)
+ {
+ var installer = new Installer(this, installers_json.get_object(), platform);
+ installers.add(installer);
+ }
+ }
+
+ return installers;
+ }
+
+ public override async void run(){ yield run_executable(); }
+
+ // public override async void uninstall()
+ // {
+ // if(install_dir != null && install_dir.query_exists())
+ // {
+ // string? uninstaller = null;
+ // try
+ // {
+ // FileInfo? finfo = null;
+ // var enumerator = yield install_dir.enumerate_children_async("standard::*", FileQueryInfoFlags.NONE);
+ // while((finfo = enumerator.next_file()) != null)
+ // {
+ // if(finfo.get_name().has_prefix("uninstall-"))
+ // {
+ // uninstaller = finfo.get_name();
+ // break;
+ // }
+ // }
+ // }
+ // catch(Error e){}
+
+ // yield umount_overlays();
+
+ // if(uninstaller != null)
+ // {
+ // uninstaller = FS.expand(install_dir.get_path(), uninstaller);
+ // debug("[GOGGame] Running uninstaller '%s'...", uninstaller);
+ // yield Utils.exec({uninstaller, "--noprompt", "--force"}).override_runtime(true).sync_thread();
+ // }
+ // else
+ // {
+ // FS.rm(install_dir.get_path(), "", "-rf");
+ // }
+ // update_status();
+ // }
+ // if((install_dir == null || !install_dir.query_exists()) && (executable == null || !executable.query_exists()))
+ // {
+ // install_dir = null;
+ // executable = null;
+ // save();
+ // update_status();
+ // }
+ // }
+
+ // public override void update_status()
+ // {
+ // if(status.state == Game.State.DOWNLOADING && status.download.status.state != Downloader.Download.State.CANCELLED) return;
+
+ // var state = Game.State.UNINSTALLED;
+
+ // var gameinfo = get_file("gameinfo");
+ // var goggame = get_file(@"goggame-$(id).info");
+ // var gh_marker = get_file(@".gamehub_$(id)");
+
+ // var files = new ArrayList<File>();
+
+ // files.add(goggame);
+ // files.add(gh_marker);
+
+ // if(!(this is DLC))
+ // {
+ // files.add(executable);
+ // files.add(gameinfo);
+ // }
+
+ // foreach(var file in files)
+ // {
+ // if(file != null && file.query_exists())
+ // {
+ // state = Game.State.INSTALLED;
+ // break;
+ // }
+ // }
+
+ // status = new Game.Status(state, this);
+ // if(state == Game.State.INSTALLED)
+ // {
+ // remove_tag(Tables.Tags.BUILTIN_UNINSTALLED);
+ // add_tag(Tables.Tags.BUILTIN_INSTALLED);
+ // }
+ // else
+ // {
+ // add_tag(Tables.Tags.BUILTIN_UNINSTALLED);
+ // remove_tag(Tables.Tags.BUILTIN_INSTALLED);
+ // }
+
+ // if(gameinfo != null && gameinfo.query_exists())
+ // {
+ // try
+ // {
+ // string info;
+ // FileUtils.get_contents(gameinfo.get_path(), out info);
+ // var lines = info.split("\n");
+ // if(lines.length >= 2)
+ // {
+ // version = lines[1];
+ // }
+ // }
+ // catch(Error e)
+ // {
+ // warning("[GOGGame.update_status] Error while reading gameinfo: %s", e.message);
+ // }
+ // }
+ // else
+ // {
+ // load_version();
+ // }
+
+ // actions.clear();
+ // if(goggame != null && goggame.query_exists())
+ // {
+ // var goggame_node = Parser.parse_json_file(goggame.get_path());
+ // if(goggame_node != null && goggame_node.get_node_type() == Json.NodeType.OBJECT)
+ // {
+ // var goggame_obj = goggame_node.get_object();
+ // var tasks = goggame_obj.has_member("playTasks") ? goggame_obj.get_array_member("playTasks") : null;
+ // if(tasks != null)
+ // {
+ // foreach(var task_node in tasks.get_elements())
+ // {
+ // if(task_node == null || task_node.get_node_type() != Json.NodeType.OBJECT) continue;
+ // var action = new RunnableAction(this, task_node.get_object());
+ // if(!action.is_hidden)
+ // {
+ // actions.add(action);
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+
+ public class Installer: Runnables.Tasks.Install.DownloadableInstaller
+ {
+ public IndieGalaGame game { get; construct set; }
+ public Json.Object json { get; construct set; }
+ public Json.Object platform_json { get; construct set; }
+ private bool fetched = false;
+
+ public Installer(IndieGalaGame game, Json.Object json, Platform platform)
+ {
+ string g = game.name;
+ Json.Object j = null;
+
+ json.get_array_member("version").foreach_element((array, index, node) => {
+ if(node.get_object().get_int_member("enabled") == 0) return;
+
+ var os = node.get_object().get_string_member("os");
+ if(platform == Platform.WINDOWS && os == "win") {
+ j = node.get_object();
+ } else if(platform == Platform.MACOS && os == "mac") {
+ j = node.get_object();
+ } else if(platform == Platform.LINUX && os == "lin") {
+ j = node.get_object();
+ }
+ });
+
+ assert(j != null);
+
+ Object(
+ game: game,
+ json: json,
+ platform_json: j,
+ id: j.get_string_member("id"),
+ name: json.get_string_member("prod_name"),
+ platform: platform,
+ version: j.get_string_member("version"),
+ installers_dir: FS.file(Settings.Paths.Collection.IndieGala.expand_installers(g, platform))
+ );
+ }
+
+ public override async void fetch_parts()
+ {
+ if(fetched || installers_dir == null) return;
+
+ // https://content.indiegalacdn.com/dev_fold_freebies/50025bf2-7190-4a32-aa15-4e4f89ba8da6/win/way-to-go_win.zip
+ var url = "https://content.indiegalacdn.com/dev_fold_"
+ + json.get_string_member("prod_dev_namespace")
+ + "/"
+ + json.get_string_member("prod_id_key_name")
+ + "/"
+ + platform_json.get_string_member("os")
+ + "/"
+ + json.get_string_member("prod_slugged_name")
+ + "_"
+ + platform_json.get_string_member("os")
+ + ".zip";
+
+
+ var remote = File.new_for_uri(url);
+ var local = installers_dir.get_child("indiegala_" + game.id + "_" + this.id);
+ parts.add(new Part(id, url, 0, remote, local));
+
+ fetched = true;
+ }
+ }
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index 16cda3c..d071d0b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -71,6 +71,9 @@ sources = [
'data/sources/humble/HumbleGame.vala',
'data/sources/humble/Trove.vala',
+ 'data/sources/indiegala/IndieGala.vala',
+ 'data/sources/indiegala/IndieGalaGame.vala',
+
'data/sources/itch/Itch.vala',
'data/sources/itch/ItchGame.vala',
'data/sources/itch/ItchDownloader.vala',
diff --git a/src/settings/Auth.vala b/src/settings/Auth.vala
index fa0b91c..d4bd7e3 100644
--- a/src/settings/Auth.vala
+++ b/src/settings/Auth.vala
@@ -109,6 +109,30 @@ namespace GameHub.Settings.Auth
}
}
+ public class IndieGala: SettingsSchema
+ {
+ public bool enabled { get; set; }
+ public bool authenticated { get; set; }
+
+ public IndieGala()
+ {
+ base(ProjectConfig.PROJECT_NAME + ".auth.indiegala");
+ }
+
+ private static IndieGala? _instance;
+ public static unowned IndieGala instance
+ {
+ get
+ {
+ if(_instance == null)
+ {
+ _instance = new IndieGala();
+ }
+ return _instance;
+ }
+ }
+ }
+
public class Itch: SettingsSchema
{
public bool enabled { get; set; }
diff --git a/src/settings/Paths.vala b/src/settings/Paths.vala
index 7582069..4383373 100644
--- a/src/settings/Paths.vala
+++ b/src/settings/Paths.vala
@@ -94,6 +94,30 @@ namespace GameHub.Settings.Paths
}
}
+ public class IndieGala: GameHub.Settings.SettingsSchema
+ {
+ public string[] game_directories { get; set; }
+ public string default_game_directory { get; set; }
+
+ public IndieGala()
+ {
+ base(ProjectConfig.PROJECT_NAME + ".paths.indiegala");
+ }
+
+ private static IndieGala _instance;
+ public static IndieGala instance
+ {
+ get
+ {
+ if(_instance == null)
+ {
+ _instance = new IndieGala();
+ }
+ return _instance;
+ }
+ }
+ }
+
public class Itch: GameHub.Settings.SettingsSchema
{
public string home { get; set; }
@@ -271,5 +295,50 @@ namespace GameHub.Settings.Paths
}
}
}
+
+ public class IndieGala: GameHub.Settings.SettingsSchema
+ {
+ public string game_dir { get; set; }
+ public string installers { get; set; }
+
+ public static string expand_game_dir(string game, Platform? platform=null)
+ {
+ var g = game.replace(": ", " - ").replace(":", "");
+ var variables = new HashMap<string, string>();
+ variables.set("root", Collection.instance.root);
+ variables.set("game", g);
+ variables.set("platform_name", platform == null ? "." : platform.name());
+ variables.set("platform", platform == null ? "." : platform.id());
+ return FS.expand(instance.game_dir, null, variables);
+ }
+ public static string expand_installers(string game, Platform? platform=null)
+ {
+ var g = game.replace(": ", " - ").replace(":", "");
+ var variables = new HashMap<string, string>();
+ variables.set("root", Collection.instance.root);
+ variables.set("platform_name", platform == null ? "." : platform.name());
+ variables.set("platform", platform == null ? "." : platform.id());
+ variables.set("game_dir", expand_game_dir(g, platform));
+ return FS.expand(instance.installers, null, variables);
+ }
+
+ public IndieGala()
+ {
+ base(ProjectConfig.PROJECT_NAME + ".paths.collection.indiegala");
+ }
+
+ private static IndieGala? _instance;
+ public static unowned IndieGala instance
+ {
+ get
+ {
+ if(_instance == null)
+ {
+ _instance = new IndieGala();
+ }
+ return _instance;
+ }
+ }
+ }
}
}
IndieGala now has a new client in beta. I don't know if they have open sourced it or not though. Also not sure if there is anything other than Windows version available.
https://www.indiegala.com/
IndieGala gives away free games on a regular basis. Integration to GameHub would certainly make things easier.