Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package heroic-gogdl for openSUSE:Factory checked in at 2026-08-24 12:10:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/heroic-gogdl (Old) and /work/SRC/openSUSE:Factory/.heroic-gogdl.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "heroic-gogdl" Mon Aug 24 12:10:37 2026 rev:9 rq:1373159 version:1.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/heroic-gogdl/heroic-gogdl.changes 2026-06-08 14:24:20.262612969 +0200 +++ /work/SRC/openSUSE:Factory/.heroic-gogdl.new.1258/heroic-gogdl.changes 2026-08-24 12:16:01.344397989 +0200 @@ -1,0 +2,11 @@ +Sat Aug 22 19:40:06 UTC 2026 - Jonatas Gonçalves <[email protected]> + +- Update to 1.3.0 + * Set path property in ScummVM config file if we're running native ScummVM + by @CommandMC in #82 + * Improve support for native DOSBox and ScummVM on macOS by @JKingweb in #85 + * Fix 83 - IPv6 and Timeouts by @Moon-yungg in #86 + * feat: cdn fallbacks by @imLinguin in d9eb7dc + * fix: cdn fallback on stream disconnection (HTTP 200) by @NicholasDymov in #88 + +------------------------------------------------------------------- Old: ---- _scmsync.obsinfo heroic-gogdl-1.2.2.tar.gz New: ---- heroic-gogdl-1.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ heroic-gogdl.spec ++++++ --- /var/tmp/diff_new_pack.8wbVIL/_old 2026-08-24 12:16:02.215429199 +0200 +++ /var/tmp/diff_new_pack.8wbVIL/_new 2026-08-24 12:16:02.216429235 +0200 @@ -16,7 +16,7 @@ # Name: heroic-gogdl -Version: 1.2.2 +Version: 1.3.0 Release: 0 Summary: GOG download module for Heroic Games Launcher License: GPL-3.0-only ++++++ heroic-gogdl-1.2.2.tar.gz -> heroic-gogdl-1.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/heroic-gogdl-1.2.2/gogdl/__init__.py new/heroic-gogdl-1.3.0/gogdl/__init__.py --- old/heroic-gogdl-1.2.2/gogdl/__init__.py 2026-06-04 17:29:32.000000000 +0200 +++ new/heroic-gogdl-1.3.0/gogdl/__init__.py 2026-08-07 16:20:08.000000000 +0200 @@ -7,4 +7,4 @@ -version = "1.2.2" +version = "1.3.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/heroic-gogdl-1.2.2/gogdl/auth.py new/heroic-gogdl-1.3.0/gogdl/auth.py --- old/heroic-gogdl-1.2.2/gogdl/auth.py 2026-06-04 17:29:32.000000000 +0200 +++ new/heroic-gogdl-1.3.0/gogdl/auth.py 2026-08-07 16:20:08.000000000 +0200 @@ -5,6 +5,7 @@ import os.path import requests import time +from gogdl import net from gogdl import version CODE_URL = "https://auth.gog.com/token?client_id=46899977096215655&client_secret=9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fembed.gog.com%2Fon_login_success%3Forigin%3Dclient&code=" @@ -14,7 +15,7 @@ class AuthorizationManager: def __init__(self, config_path): - self.session = requests.session() + self.session = net.Session() self.logger = logging.getLogger("AUTH") self.config_path = config_path @@ -120,7 +121,13 @@ self.logger.debug("Handling cli") if arguments.authorization_code: - response = self.session.get(CODE_URL + arguments.authorization_code) + try: + response = self.session.get(CODE_URL + arguments.authorization_code) + except (requests.ConnectionError, requests.Timeout) as e: + # Don't log the exception itself because it contains client id and client secret + self.logger.error(f"Failed to reach GOG ({type(e).__name__})") + print(json.dumps({"error": True})) + return if not response.ok: print(json.dumps({"error": True})) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/heroic-gogdl-1.2.2/gogdl/dl/workers/task_executor.py new/heroic-gogdl-1.3.0/gogdl/dl/workers/task_executor.py --- old/heroic-gogdl-1.2.2/gogdl/dl/workers/task_executor.py 2026-06-04 17:29:32.000000000 +0200 +++ new/heroic-gogdl-1.3.0/gogdl/dl/workers/task_executor.py 2026-08-07 16:20:08.000000000 +0200 @@ -27,6 +27,7 @@ UNAUTHORIZED = auto() MISSING_CHUNK = auto() + INCOMPLETE_READ = auto() @dataclass @@ -110,33 +111,48 @@ self.session.close() self.shared_memory.close() - def v2(self, task: DownloadTask2): - retries = 5 - urls = self.secure_links[task.product_id] - - compressed_md5 = task.compressed_sum - - endpoint = copy(urls[0]) + def _get_download_url_v2(self, task, urls, index): + if len(urls) <= index: + index = len(urls) - 1 + endpoint = copy(urls[index]) if task.product_id != 'redist': - endpoint["parameters"]["path"] += f"/{dl_utils.galaxy_path(compressed_md5)}" + endpoint["parameters"]["path"] += f"/{dl_utils.galaxy_path(task.compressed_sum)}" url = dl_utils.merge_url_with_params( endpoint["url_format"], endpoint["parameters"] ) else: - endpoint["url"] += "/" + dl_utils.galaxy_path(compressed_md5) + endpoint["url"] += "/" + dl_utils.galaxy_path(task.compressed_sum) url = endpoint["url"] + return url - buffer = bytes() - compressed_sum = hashlib.md5() - download_size = 0 - response = None + def _get_download_url_v1(self, urls): + if type(urls) == str: + url = urls + else: + endpoint = copy(urls[0]) + endpoint["parameters"]["path"] += "/main.bin" + url = dl_utils.merge_url_with_params( + endpoint["url_format"], endpoint["parameters"] + ) + return url + + def v2(self, task: DownloadTask2): + retries = 5 + urls = self.secure_links[task.product_id] + + compressed_md5 = task.compressed_sum + preferred_endpoint = 0 + url = self._get_download_url_v2(task, urls, preferred_endpoint) + + fail_reason = None while retries > 0: + response = None buffer = bytes() compressed_sum = hashlib.md5() download_size = 0 decompressor = zlib.decompressobj() try: - response = self.session.get(url, stream=True, timeout=10) + response = self.session.get(url, stream=True, timeout=(5, 15)) response.raise_for_status() for chunk in response.iter_content(1024 * 512): download_size += len(chunk) @@ -144,19 +160,30 @@ decompressed = decompressor.decompress(chunk) buffer += decompressed self.speed_queue.put((len(chunk), len(decompressed))) - - except Exception as e: + except (requests.exceptions.HTTPError, requests.exceptions.ConnectTimeout) as e: print("Connection failed", e) - if response and response.status_code == 401: + if response and response.status_code in [401, 403]: self.results_queue.put(DownloadTaskResult(False, FailReason.UNAUTHORIZED, task)) print("Connection failed, unauthorized") return - retries -= 1 + else: + fail_reason = FailReason.CONNECTION + except requests.exceptions.RequestException as e: + print("Connection failed", e) + fail_reason = FailReason.INCOMPLETE_READ + except Exception as e: + print("Connection failed", e) + fail_reason = FailReason.UNKNOWN + else: + break + preferred_endpoint += 1 + if preferred_endpoint >= len(urls): + preferred_endpoint = 0 time.sleep(2) - continue - break + url = self._get_download_url_v2(task, urls, preferred_endpoint) + retries -= 1 else: - self.results_queue.put(DownloadTaskResult(False, FailReason.CHECKSUM, task)) + self.results_queue.put(DownloadTaskResult(False, fail_reason, task)) return decompressed_size = 0 @@ -180,18 +207,12 @@ urls = self.secure_links[task.product_id] response = None - if type(urls) == str: - url = urls - else: - endpoint = copy(urls[0]) - endpoint["parameters"]["path"] += "/main.bin" - url = dl_utils.merge_url_with_params( - endpoint["url_format"], endpoint["parameters"] - ) + url = self._get_download_url_v1(urls) range_header = dl_utils.get_range_header(task.offset, task.size) buffer = bytes() while retries > 0: + response = None buffer = bytes() try: response = self.session.get(url, stream=True, timeout=10, headers={'Range': range_header}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/heroic-gogdl-1.2.2/gogdl/launch.py new/heroic-gogdl-1.3.0/gogdl/launch.py --- old/heroic-gogdl-1.2.2/gogdl/launch.py 2026-06-04 17:29:32.000000000 +0200 +++ new/heroic-gogdl-1.3.0/gogdl/launch.py 2026-08-07 16:20:08.000000000 +0200 @@ -1,3 +1,4 @@ +import configparser import os import json import sys @@ -39,6 +40,20 @@ pass return [] +def get_app_bundle_command(id: str) -> list[str]: + if sys.platform != "darwin": + return [] + try: + p = subprocess.run(["mdfind", "kMDItemCFBundleIdentifier==", id], stdout=subprocess.PIPE, stderr=None) + if len(p.stdout) > 0: + bundle = p.stdout.split(b"\n")[0].decode("utf-8") + p = subprocess.run(["defaults", "read", bundle + "/Contents/Info.plist", "CFBundleExecutable"], stdout=subprocess.PIPE, stderr=None) + if len(p.stdout) > 0: + bin = p.stdout.split(b"\n")[0].decode("utf-8") + return [bundle + "/Contents/MacOS/" + bin] + except FileNotFoundError: + pass + return [] # Supports launching linux builds def launch(arguments, unknown_args): @@ -95,21 +110,44 @@ if sys.platform != "win32" and arguments.platform == 'windows' and not arguments.override_exe: if "scummvm.exe" in executable.lower(): flatpak_scummvm = get_flatpak_command("org.scummvm.ScummVM") + bundle_scummvm = get_app_bundle_command("org.scummvm.app") native_scummvm = shutil.which("scummvm") if native_scummvm: native_scummvm = [native_scummvm] - native_runner = flatpak_scummvm or native_scummvm + native_runner = flatpak_scummvm or bundle_scummvm or native_scummvm if native_runner: wrapper = native_runner executable = None + # ScummVM games require a "path" option to be set in their configuration file (pointing to the + # game installation dir). This path is usually set by ScriptInterpreter, but since SI is a Windows + # application, this path will be wrong. Open the config file and update the path + config_file = next(( + arg + for i, arg in enumerate(launch_arguments) + if launch_arguments[i-1] == '-c' + ), None) + config_section = launch_arguments[-1] + if config_file and config_section: + full_config_file = os.path.join(working_dir, config_file) + config = configparser.ConfigParser() + config.read(full_config_file) + config.set(config_section, 'path', arguments.path) + with open(full_config_file, 'w') as f: + config.write(f) elif "dosbox.exe" in executable.lower(): flatpak_dosbox = get_flatpak_command("io.github.dosbox-staging") - native_dosbox= shutil.which("dosbox") - if native_dosbox: - native_dosbox = [native_dosbox] - - native_runner = flatpak_dosbox or native_dosbox + bundle_dosbox = get_app_bundle_command("io.github.dosbox-staging") + for candidate in ["dosbox-staging", "dosbox"]: + # Most distributions prefer "dosbox" for DOSBox Staging's + # binary and let different DOSBox variants conflict, but + # Homebrew in particular uses "dosbox-staging". As the + # latter is more specific we try that first. + native_dosbox= shutil.which(candidate) + if native_dosbox: + native_dosbox = [native_dosbox] + break + native_runner = flatpak_dosbox or bundle_dosbox or native_dosbox if native_runner: wrapper = native_runner executable = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/heroic-gogdl-1.2.2/gogdl/net.py new/heroic-gogdl-1.3.0/gogdl/net.py --- old/heroic-gogdl-1.2.2/gogdl/net.py 1970-01-01 01:00:00.000000000 +0100 +++ new/heroic-gogdl-1.3.0/gogdl/net.py 2026-08-07 16:20:08.000000000 +0200 @@ -0,0 +1,13 @@ +# Add request timeouts. Matters when ipv6 is given by ISP but not routed +# python will automatically fallback to ipv4 when the ipv6 request timeouts +import requests + +# connect, read +TIMEOUT = (10, 30) + + +class Session(requests.Session): + def request(self, method, url, **kwargs): + if kwargs.get("timeout") is None: + kwargs["timeout"] = TIMEOUT + return super().request(method, url, **kwargs)
