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-06-08 14:17:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/heroic-gogdl (Old)
 and      /work/SRC/openSUSE:Factory/.heroic-gogdl.new.2375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "heroic-gogdl"

Mon Jun  8 14:17:55 2026 rev:8 rq:1357623 version:1.2.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/heroic-gogdl/heroic-gogdl.changes        
2026-04-25 21:35:45.969854566 +0200
+++ /work/SRC/openSUSE:Factory/.heroic-gogdl.new.2375/heroic-gogdl.changes      
2026-06-08 14:24:20.262612969 +0200
@@ -1,0 +2,13 @@
+Fri Jun  5 22:50:15 UTC 2026 - Jonatas Gonçalves <[email protected]>
+
+- Update to version 1.2.2
+  * fix: prevent .netrc credentials from being injected into GOG auth by 
@badboybeyer in #75
+  * Add more logging to --debug level by @izacus in #79
+  * fix: prevent multiple CLI crashes and improve error handling by 
@Jonatas-Goncalves in #76
+  * fix: do case insensitive comparison of deprecated codes by @imLinguin in 
eee0bbb
+  * ci: package as zipapp on linux by @imLinguin in 2ed0d06
+  * Update use-system-xdelta3.patch to fit upstream
+  * Remove pyproject.toml modifications performed via sed
+  * Drop xdelta extension definition from pyproject.toml via patch
+
+-------------------------------------------------------------------

Old:
----
  heroic-gogdl-1.2.1.tar.gz

New:
----
  heroic-gogdl-1.2.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ heroic-gogdl.spec ++++++
--- /var/tmp/diff_new_pack.vCF7nI/_old  2026-06-08 14:24:21.190651452 +0200
+++ /var/tmp/diff_new_pack.vCF7nI/_new  2026-06-08 14:24:21.190651452 +0200
@@ -16,7 +16,7 @@
 #
 
 Name:             heroic-gogdl
-Version:          1.2.1
+Version:          1.2.2
 Release:          0
 Summary:          GOG download module for Heroic Games Launcher
 License:          GPL-3.0-only
@@ -43,11 +43,6 @@
 %prep
 %autosetup -p1
 
-rm -f gogdl/xdelta3.c
-
-sed -i '/\[tool.setuptools.ext-modules\]/,/\]/d' pyproject.toml
-sed -i '/xdelta3/d' pyproject.toml
-
 find . -name "*.py" -exec sed -i '1{/^#!/d}' {} +
 
 %build

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.vCF7nI/_old  2026-06-08 14:24:21.222652779 +0200
+++ /var/tmp/diff_new_pack.vCF7nI/_new  2026-06-08 14:24:21.226652945 +0200
@@ -1,5 +1,5 @@
-mtime: 1777001046
-commit: 6a51091ee35f937f7a1e694ee3fa36c44a555766c7acb2ceb579f8d2a526bb0d
+mtime: 1780700061
+commit: 54ce65ccefae87c51e70186893e9905a0213a447ccbf9a7ef3134b4d1ee9ec3c
 url: https://src.opensuse.org/MaxxedSUSE/heroic-gogdl
 revision: master
 

++++++ heroic-gogdl-1.2.1.tar.gz -> heroic-gogdl-1.2.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/.github/workflows/build.yaml 
new/heroic-gogdl-1.2.2/.github/workflows/build.yaml
--- old/heroic-gogdl-1.2.1/.github/workflows/build.yaml 2026-02-06 
15:17:40.000000000 +0100
+++ new/heroic-gogdl-1.2.2/.github/workflows/build.yaml 2026-06-04 
17:29:32.000000000 +0200
@@ -17,7 +17,7 @@
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15-intel, macos-15, 
windows-2025, windows-11-arm]
+        os: [macos-15-intel, macos-15, windows-2025, windows-11-arm]
     runs-on: ${{ matrix.os }}
 
     steps:
@@ -53,8 +53,40 @@
           name: gogdl-${{ matrix.os }}
           path: dist/*
 
+  zipapp:
+    strategy:
+      matrix:
+        os: [ubuntu-24.04, ubuntu-24.04-arm]
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      - uses: actions/checkout@v5
+        with:
+          submodules: 'true'
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.13"
+      
+      - name: Install gogdl into zipapp dir
+        run: pip3 install . --target build 
+      
+      - run: mkdir -p dist
+
+      - name: Copy files
+        run: |
+          cp zipapp_main.py build/__main__.py
+
+      - name: Build
+        run: python -m zipapp --output dist/gogdl --python "/usr/bin/env 
python3" --compress build
+
+      - uses: actions/upload-artifact@v6
+        with:
+          name: gogdl-${{ matrix.os }}
+          path: dist/*
+
+
   draft:
-    needs: build
+    needs: [build, zipapp]
     if: ${{ github.ref_type == 'tag' }}
     runs-on: ubuntu-latest
     
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/README.md 
new/heroic-gogdl-1.2.2/README.md
--- old/heroic-gogdl-1.2.1/README.md    2026-02-06 15:17:40.000000000 +0100
+++ new/heroic-gogdl-1.2.2/README.md    2026-06-04 17:29:32.000000000 +0200
@@ -47,10 +47,33 @@
 - Build the binary (assuming you are in heroic-gogdl direcory)
 
 ```bash
-pip install -e . # Ensure you build the C code to python module
+pip install -e . # Ensure you build the C code to python module in current 
directory
 pyinstaller --onefile --name gogdl gogdl/cli.py
 ```
 
+## Building zipapp executable
+
+For Linux it is especially recommended to use zipapp format, as it allows 
gogdl by relying on OS provided python interpretter
+
+- Install gogdl and its dependencies into build directory
+
+```bash
+pip install . --target build 
+```
+
+- Copy custom entry point - it's required to unpack the C lib to a known 
location
+
+Right now the entry point is hardcoded for Linux support only
+```bash
+cp zipapp_main.py build/__main__.py
+``` 
+
+- Package
+
+```bash
+python -m zipapp --output dist/gogdl --python "/usr/bin/env python3" 
--compress build
+```
+
 ## Great resources about GOG API
 
 - https://github.com/Lariaa/GameLauncherResearch/wiki/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/__init__.py 
new/heroic-gogdl-1.2.2/gogdl/__init__.py
--- old/heroic-gogdl-1.2.1/gogdl/__init__.py    2026-02-06 15:17:40.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/gogdl/__init__.py    2026-06-04 17:29:32.000000000 
+0200
@@ -7,4 +7,4 @@
 
 
 
-version = "1.2.1"
+version = "1.2.2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/auth.py 
new/heroic-gogdl-1.2.2/gogdl/auth.py
--- old/heroic-gogdl-1.2.1/gogdl/auth.py        2026-02-06 15:17:40.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/gogdl/auth.py        2026-06-04 17:29:32.000000000 
+0200
@@ -24,9 +24,10 @@
         self.session.headers.update(
             {"User-Agent": f"gogdl/{version} (Heroic Games Launcher)"}
         )
+        self.session.auth = lambda r: r
 
     def __read_config(self):
-        if os.path.exists(self.config_path):
+        if self.config_path and os.path.exists(self.config_path):
             with open(self.config_path, "r") as f:
                 self.credentials_data = json.loads(f.read())
                 f.close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/cli.py 
new/heroic-gogdl-1.2.2/gogdl/cli.py
--- old/heroic-gogdl-1.2.1/gogdl/cli.py 2026-02-06 15:17:40.000000000 +0100
+++ new/heroic-gogdl-1.2.2/gogdl/cli.py 2026-06-04 17:29:32.000000000 +0200
@@ -29,6 +29,7 @@
     if '-d' in unknown_args or '--debug' in unknown_args:
         level = logging.DEBUG
     logging.basicConfig(format="[%(name)s] %(levelname)s: %(message)s", 
level=level)
+    logging.getLogger("urllib3").setLevel(level)
     logger = logging.getLogger("MAIN")
     logger.debug(arguments)
     if arguments.display_version:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/dl/managers/linux.py 
new/heroic-gogdl-1.2.2/gogdl/dl/managers/linux.py
--- old/heroic-gogdl-1.2.1/gogdl/dl/managers/linux.py   2026-02-06 
15:17:40.000000000 +0100
+++ new/heroic-gogdl-1.2.2/gogdl/dl/managers/linux.py   2026-06-04 
17:29:32.000000000 +0200
@@ -22,6 +22,9 @@
         
f"{constants.GOG_CONTENT_SYSTEM}/products/{id}/os/windows/builds?generation=2",
     )
 
+    if not builds.get("items"):
+        return "UnknownGame"
+
     url = builds["items"][0]["link"]
     meta, headers = dl_utils.get_zlib_encoded(api_handler, url)
     install_dir = (
@@ -62,7 +65,7 @@
         self.logger.info("Initialized Linux Download Manager")
 
         self.game_data = None
-
+        self.game_installer = {"version": "Unknown"}
         self.languages_codes = list()
         self.downlink = None
         self.game_files = list()
@@ -90,6 +93,11 @@
     def setup(self):
         self.game_data = self.api_handler.get_item_data(self.game_id, 
expanded=['downloads', 'expanded_dlcs'])
 
+        if not self.game_data: # Adicionado este bloco de proteção
+            self.logger.error("Could not fetch game data. Check your 
connection or auth.")
+            self.game_data = {"downloads": {"installers": []}, 
"expanded_dlcs": []}
+            return
+
         # Filter linux installers
         game_installers = 
self.filter_linux_installers(self.game_data["downloads"]["installers"])
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/dl/workers/task_executor.py 
new/heroic-gogdl-1.2.2/gogdl/dl/workers/task_executor.py
--- old/heroic-gogdl-1.2.1/gogdl/dl/workers/task_executor.py    2026-02-06 
15:17:40.000000000 +0100
+++ new/heroic-gogdl-1.2.2/gogdl/dl/workers/task_executor.py    2026-06-04 
17:29:32.000000000 +0200
@@ -17,7 +17,7 @@
 from enum import Enum, auto
 from multiprocessing import Process, Queue
 from gogdl.dl.objects.generic import MemorySegment, TaskFlag, TerminateWorker
-import gogdl.xdelta3
+import gogdl_xdelta3
 
 
 class FailReason(Enum):
@@ -357,7 +357,7 @@
                     patch = os.path.join(task.destination, task.patch_file)
                     patch = dl_utils.get_case_insensitive_name(patch)
                     target = task_path
-                    gogdl.xdelta3.patch(source, patch, target, 
self.speed_queue)
+                    gogdl_xdelta3.patch(source, patch, target, 
self.speed_queue)
 
                 except Exception as e:
                     print("Patch failed", e)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/languages.py 
new/heroic-gogdl-1.2.2/gogdl/languages.py
--- old/heroic-gogdl-1.2.1/gogdl/languages.py   2026-02-06 15:17:40.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/gogdl/languages.py   2026-06-04 17:29:32.000000000 
+0200
@@ -15,9 +15,9 @@
         # If comparing to string, look for the code, name and deprecated code
         if type(value) is str:
             return (
-                value == self.code
+                value.lower() == self.code.lower()
                 or value.lower() == self.name.lower()
-                or value in self.deprecated_codes
+                or value.lower() in [l.lower() for l in self.deprecated_codes]
             )
         return NotImplemented
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/saves.py 
new/heroic-gogdl-1.2.2/gogdl/saves.py
--- old/heroic-gogdl-1.2.1/gogdl/saves.py       2026-02-06 15:17:40.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/gogdl/saves.py       2026-06-04 17:29:32.000000000 
+0200
@@ -221,6 +221,7 @@
         json_res = response.json()
         # print(json_res)
         self.logger.info(f"Files in cloud: {len(json_res)}")
+        self.logger.debug(f"Files: {json_res}")
 
         filtered = filter(self.is_in_our_dir, json_res)
 
@@ -255,6 +256,7 @@
         response = self.session.delete(
             
f"{constants.GOG_CLOUDSTORAGE}/v1/{self.credentials['user_id']}/{self.client_id}/{self.cloud_save_dir_name}/{fpath}",
         )
+        self.logger.debug(f"Delete response: {response}")
 
     def upload_file(self, file: SyncFile):
         compressed_data = gzip.compress(
@@ -296,6 +298,8 @@
 
         if not response.ok:
             self.logger.error("Downloading file failed")
+            self.logger.debug(f"Download error: {response}")
+            return
 
         total = response.headers.get("Content-Length")
         os.makedirs(os.path.split(file.absolute_path)[0], exist_ok=True)
@@ -325,6 +329,7 @@
         response = 
self.session.post(f"{constants.GOG_CLOUDSTORAGE}/v1/{self.credentials['user_id']}/{self.client_id}")
         if not response.ok:
             self.logger.error("Failed to commit")
+            self.logger.debug(f"Commit error: {response}")
 
 
 class SyncClassifier:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl/xdelta3.c 
new/heroic-gogdl-1.2.2/gogdl/xdelta3.c
--- old/heroic-gogdl-1.2.1/gogdl/xdelta3.c      2026-02-06 15:17:40.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/gogdl/xdelta3.c      1970-01-01 01:00:00.000000000 
+0100
@@ -1,271 +0,0 @@
-#include <Python.h>
-#ifdef _LARGEFILE_SOURCE
-#undef _LARGEFILE_SOURCE
-#endif
-#include <xdelta3/xdelta3.h>
-
-#define BLOCK_SIZE 1 << 23
-#define BLOCK_CACHE_SIZE 32
-
-struct cache_nav {
-  struct cache_nav *next;
-  struct cache_nav *prev;
-};
-
-struct cache {
-  xoff_t blkno;
-  usize_t onblk;
-  uint8_t *blk;
-  struct cache_nav nav;
-};
-
-static inline struct cache* cache_entry(struct cache_nav *l) {
-  return (struct cache*) ((char*) l - (ptrdiff_t) &((struct cache*) 0)->nav);
-}
-
-static inline void cache_init(struct cache_nav *l) {
-  l->prev = l;
-  l->next = l;
-}
-static inline void cache_add(struct cache_nav *prev, struct cache_nav *next,
-                      struct cache_nav *ins) {
-  next->prev = ins;
-  prev->next = ins;
-  ins->next = next;
-  ins->prev = prev;
-}
-
-static inline struct cache* cache_pop_front(struct cache_nav *l) {
-  struct cache_nav* i = l->next;
-  i->next->prev = i->prev;
-  i->prev->next = i->next;
-  return cache_entry(i);
-}
-
-static inline struct cache* cache_remove(struct cache *f) {
-  struct cache_nav *i = f->nav.next;
-  f->nav.next->prev = f->nav.prev;
-  f->nav.prev->next = f->nav.next;
-  return cache_entry(i);
-}
-
-
-void put_progress(PyObject *queue, usize_t written, usize_t read) {
-  PyObject *progress_tuple = NULL;
-  PyObject *put_result = NULL;
-
-  PyObject *written_obj = NULL;
-  PyObject *read_obj = NULL;
-
-  PyObject *put_method = PyObject_GetAttrString(queue, "put");
-  if (!put_method || !PyCallable_Check(put_method)) {
-    PyErr_SetString(PyExc_TypeError, "'put' is not callable");
-    Py_XDECREF(put_method);
-    return;
-  }
-
-  read_obj = PyLong_FromLong(read);
-  written_obj = PyLong_FromLong(written);
-
-  if (!written_obj || !read_obj) {
-    Py_XDECREF(written_obj);
-    Py_XDECREF(read_obj);
-    Py_DECREF(put_method);
-    return;
-  }
-
-  progress_tuple = PyTuple_New(2);
-  if (!progress_tuple) {
-    Py_DECREF(written_obj);
-    Py_DECREF(read_obj);
-    Py_DECREF(put_method);
-    return;
-  }
-
-  PyTuple_SetItem(progress_tuple, 0, written_obj);
-  PyTuple_SetItem(progress_tuple, 1, read_obj);
-
-  put_result = PyObject_CallFunctionObjArgs(put_method, progress_tuple, NULL);
-
-  Py_DECREF(put_method);
-  Py_DECREF(progress_tuple);
-  Py_DECREF(put_result);
-}
-
-static PyObject *patch(PyObject *self, PyObject *args) {
-  const char *source;
-  const char *patch;
-  const char *target;
-  PyObject *queue;
-
-  xd3_stream stream;
-  xd3_config config;
-  xd3_source src;
-  uint8_t *input_buffer = NULL;
-  struct cache *block_cache = NULL;
-  struct cache_nav block_cache_nav;
-
-  FILE *fsource = NULL;
-  FILE *fpatch = NULL;
-  FILE *ftarget = NULL;
-
-  usize_t input_read = 0;
-  uint64_t offset = 0;
-  usize_t cache_size = 0;
-
-  usize_t written = 0;
-  usize_t read = 0;
-
-
-  if (!PyArg_ParseTuple(args, "sssO", &source, &patch, &target, &queue)) {
-    return NULL;
-  }
-  if (!PyObject_HasAttrString(queue, "put")) {
-    PyErr_SetString(PyExc_TypeError,
-                    "Expected a queue-like object with a .put() method");
-    return NULL;
-  }
-  cache_init(&block_cache_nav);
-  input_buffer = malloc(BLOCK_SIZE);
-  block_cache = malloc(BLOCK_CACHE_SIZE * sizeof(struct cache));
-  if (!block_cache) {
-    PyErr_SetFromErrno(PyExc_MemoryError);
-    goto cleanup;
-  }
-  memset(block_cache, 0, sizeof(block_cache[0]) * BLOCK_CACHE_SIZE);
-  block_cache[0].blk = malloc((BLOCK_SIZE) * BLOCK_CACHE_SIZE);
-  if (!block_cache[0].blk) {
-    PyErr_SetFromErrno(PyExc_MemoryError);
-    goto cleanup;
-  }
-  cache_size = BLOCK_CACHE_SIZE;
-  for (int i=0; i<cache_size;i++) {
-    block_cache[i].blkno = -1;
-    if (i>0) block_cache[i].blk = block_cache[0].blk + (i * BLOCK_SIZE);
-    cache_add(block_cache_nav.prev, &block_cache_nav, &block_cache[i].nav);
-  }
-
-  Py_BEGIN_ALLOW_THREADS if (!(fsource = fopen(source, "rb"))) {
-    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_OSError);
-    Py_UNBLOCK_THREADS goto cleanup;
-  }
-
-  if (!(fpatch = fopen(patch, "rb"))) {
-    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_OSError);
-    Py_UNBLOCK_THREADS goto cleanup;
-  }
-
-  if (!(ftarget = fopen(target, "wb"))) {
-    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_OSError);
-    Py_UNBLOCK_THREADS goto cleanup;
-  }
-
-  memset(&stream, 0, sizeof(stream));
-  memset(&config, 0, sizeof(config));
-  memset(&src, 0, sizeof(src));
-
-  config.winsize = XD3_DEFAULT_WINSIZE;
-  xd3_config_stream(&stream, &config);
-
-  src.blksize = BLOCK_SIZE;
-  src.curblk = block_cache[0].blk;
-  src.curblkno = 0;
-  block_cache[0].blkno = 0;
-  src.onblk = fread(src.curblk, sizeof(uint8_t), BLOCK_SIZE, fsource);
-  xd3_set_source(&stream, &src);
-  block_cache[0].onblk = src.onblk;
-
-  do {
-    input_read = fread(input_buffer, sizeof(uint8_t), BLOCK_SIZE, fpatch);
-    if (input_read < BLOCK_SIZE) {
-      xd3_set_flags(&stream, XD3_FLUSH);
-    }
-    xd3_avail_input(&stream, input_buffer, input_read);
-  process:
-    switch (xd3_decode_input(&stream)) {
-    case XD3_INPUT:
-      continue;
-    case XD3_OUTPUT:
-      fwrite(stream.next_out, sizeof(uint8_t), stream.avail_out, ftarget);
-      xd3_consume_output(&stream);
-      goto process;
-    case XD3_GETSRCBLK: {
-      for (int i = 0; i < cache_size; i++) {
-        if (block_cache[i].blkno == src.getblkno) {
-          src.onblk = block_cache[i].onblk;
-          src.curblk = block_cache[i].blk;
-          src.curblkno = src.getblkno;
-          cache_remove(&block_cache[i]);
-          cache_add(block_cache_nav.prev, &block_cache_nav, 
&block_cache[i].nav);
-          goto process;
-        }
-      }
-      struct cache* cache_el = cache_pop_front(&block_cache_nav);
-      cache_add(block_cache_nav.prev, &block_cache_nav, &cache_el->nav);
-
-      offset = src.blksize * src.getblkno;
-      fseek(fsource, offset, SEEK_SET);
-      cache_el->onblk = fread(
-          cache_el->blk, sizeof(uint8_t), src.blksize, fsource);
-      cache_el->blkno = src.getblkno;
-
-      src.curblkno = cache_el->blkno;
-      src.onblk = cache_el->onblk;
-      src.curblk = cache_el->blk;
-
-      goto process;
-    }
-    case XD3_GOTHEADER:
-    case XD3_WINSTART:
-    case XD3_WINFINISH:
-      /* no action necessary */
-      Py_BLOCK_THREADS
-      put_progress(queue, stream.total_out - written, stream.total_in - read);
-      written = stream.total_out;
-      read = stream.total_in;
-      Py_UNBLOCK_THREADS
-      goto process;
-    default:
-      Py_BLOCK_THREADS if (stream.msg) {
-        printf("%s\n", stream.msg);
-        fflush(stdout);
-      }
-      PyErr_SetFromErrno(PyExc_MemoryError);
-      Py_UNBLOCK_THREADS goto cleanup;
-    }
-
-  } while (input_read == BLOCK_SIZE);
-  if (xd3_close_stream(&stream)) {
-    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_AssertionError);
-    Py_UNBLOCK_THREADS
-  }
-
-cleanup:
-  Py_END_ALLOW_THREADS xd3_free_stream(&stream);
-  if (block_cache) {
-    if (block_cache[0].blk) free(block_cache[0].blk);
-    free(block_cache);
-  }
-
-  if (input_buffer)
-    free(input_buffer);
-  if (fsource)
-    fclose(fsource);
-  if (fpatch)
-    fclose(fpatch);
-  if (ftarget) {
-    fflush(ftarget);
-    fclose(ftarget);
-  }
-
-  Py_RETURN_NONE;
-}
-
-static PyMethodDef methods[] = {
-    {"patch", patch, METH_VARARGS, "Runs a patch on provided files"},
-    {NULL, NULL, 0, NULL}};
-
-static struct PyModuleDef xdelta_def = {PyModuleDef_HEAD_INIT, "xdelta3", NULL,
-                                        -1, methods};
-
-PyMODINIT_FUNC PyInit_xdelta3(void) { return PyModule_Create(&xdelta_def); }
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/gogdl_xdelta3.c 
new/heroic-gogdl-1.2.2/gogdl_xdelta3.c
--- old/heroic-gogdl-1.2.1/gogdl_xdelta3.c      1970-01-01 01:00:00.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/gogdl_xdelta3.c      2026-06-04 17:29:32.000000000 
+0200
@@ -0,0 +1,274 @@
+#ifndef Py_LIMITED_API
+#error "Py_LIMITED_API must be defined! We rely on it to ensure we attempt to 
use stable 3.x ABI"
+#endif
+#include <Python.h>
+#ifdef _LARGEFILE_SOURCE
+#undef _LARGEFILE_SOURCE
+#endif
+#include <xdelta3/xdelta3.h>
+
+#define BLOCK_SIZE 1 << 23
+#define BLOCK_CACHE_SIZE 32
+
+struct cache_nav {
+  struct cache_nav *next;
+  struct cache_nav *prev;
+};
+
+struct cache {
+  xoff_t blkno;
+  usize_t onblk;
+  uint8_t *blk;
+  struct cache_nav nav;
+};
+
+static inline struct cache* cache_entry(struct cache_nav *l) {
+  return (struct cache*) ((char*) l - (ptrdiff_t) &((struct cache*) 0)->nav);
+}
+
+static inline void cache_init(struct cache_nav *l) {
+  l->prev = l;
+  l->next = l;
+}
+static inline void cache_add(struct cache_nav *prev, struct cache_nav *next,
+                      struct cache_nav *ins) {
+  next->prev = ins;
+  prev->next = ins;
+  ins->next = next;
+  ins->prev = prev;
+}
+
+static inline struct cache* cache_pop_front(struct cache_nav *l) {
+  struct cache_nav* i = l->next;
+  i->next->prev = i->prev;
+  i->prev->next = i->next;
+  return cache_entry(i);
+}
+
+static inline struct cache* cache_remove(struct cache *f) {
+  struct cache_nav *i = f->nav.next;
+  f->nav.next->prev = f->nav.prev;
+  f->nav.prev->next = f->nav.next;
+  return cache_entry(i);
+}
+
+
+void put_progress(PyObject *queue, usize_t written, usize_t read) {
+  PyObject *progress_tuple = NULL;
+  PyObject *put_result = NULL;
+
+  PyObject *written_obj = NULL;
+  PyObject *read_obj = NULL;
+
+  PyObject *put_method = PyObject_GetAttrString(queue, "put");
+  if (!put_method || !PyCallable_Check(put_method)) {
+    PyErr_SetString(PyExc_TypeError, "'put' is not callable");
+    Py_XDECREF(put_method);
+    return;
+  }
+
+  read_obj = PyLong_FromLong(read);
+  written_obj = PyLong_FromLong(written);
+
+  if (!written_obj || !read_obj) {
+    Py_XDECREF(written_obj);
+    Py_XDECREF(read_obj);
+    Py_DECREF(put_method);
+    return;
+  }
+
+  progress_tuple = PyTuple_New(2);
+  if (!progress_tuple) {
+    Py_DECREF(written_obj);
+    Py_DECREF(read_obj);
+    Py_DECREF(put_method);
+    return;
+  }
+
+  PyTuple_SetItem(progress_tuple, 0, written_obj);
+  PyTuple_SetItem(progress_tuple, 1, read_obj);
+
+  put_result = PyObject_CallFunctionObjArgs(put_method, progress_tuple, NULL);
+
+  Py_DECREF(put_method);
+  Py_DECREF(progress_tuple);
+  Py_DECREF(put_result);
+}
+
+static PyObject *patch(PyObject *self, PyObject *args) {
+  const char *source;
+  const char *patch;
+  const char *target;
+  PyObject *queue;
+
+  xd3_stream stream;
+  xd3_config config;
+  xd3_source src;
+  uint8_t *input_buffer = NULL;
+  struct cache *block_cache = NULL;
+  struct cache_nav block_cache_nav;
+
+  FILE *fsource = NULL;
+  FILE *fpatch = NULL;
+  FILE *ftarget = NULL;
+
+  usize_t input_read = 0;
+  uint64_t offset = 0;
+  usize_t cache_size = 0;
+
+  usize_t written = 0;
+  usize_t read = 0;
+
+
+  if (!PyArg_ParseTuple(args, "sssO", &source, &patch, &target, &queue)) {
+    return NULL;
+  }
+  if (!PyObject_HasAttrString(queue, "put")) {
+    PyErr_SetString(PyExc_TypeError,
+                    "Expected a queue-like object with a .put() method");
+    return NULL;
+  }
+  cache_init(&block_cache_nav);
+  input_buffer = malloc(BLOCK_SIZE);
+  block_cache = malloc(BLOCK_CACHE_SIZE * sizeof(struct cache));
+  if (!block_cache) {
+    PyErr_SetFromErrno(PyExc_MemoryError);
+    goto cleanup;
+  }
+  memset(block_cache, 0, sizeof(block_cache[0]) * BLOCK_CACHE_SIZE);
+  block_cache[0].blk = malloc((BLOCK_SIZE) * BLOCK_CACHE_SIZE);
+  if (!block_cache[0].blk) {
+    PyErr_SetFromErrno(PyExc_MemoryError);
+    goto cleanup;
+  }
+  cache_size = BLOCK_CACHE_SIZE;
+  for (int i=0; i<cache_size;i++) {
+    block_cache[i].blkno = -1;
+    if (i>0) block_cache[i].blk = block_cache[0].blk + (i * BLOCK_SIZE);
+    cache_add(block_cache_nav.prev, &block_cache_nav, &block_cache[i].nav);
+  }
+
+  Py_BEGIN_ALLOW_THREADS if (!(fsource = fopen(source, "rb"))) {
+    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_OSError);
+    Py_UNBLOCK_THREADS goto cleanup;
+  }
+
+  if (!(fpatch = fopen(patch, "rb"))) {
+    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_OSError);
+    Py_UNBLOCK_THREADS goto cleanup;
+  }
+
+  if (!(ftarget = fopen(target, "wb"))) {
+    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_OSError);
+    Py_UNBLOCK_THREADS goto cleanup;
+  }
+
+  memset(&stream, 0, sizeof(stream));
+  memset(&config, 0, sizeof(config));
+  memset(&src, 0, sizeof(src));
+
+  config.winsize = XD3_DEFAULT_WINSIZE;
+  xd3_config_stream(&stream, &config);
+
+  src.blksize = BLOCK_SIZE;
+  src.curblk = block_cache[0].blk;
+  src.curblkno = 0;
+  block_cache[0].blkno = 0;
+  src.onblk = fread(src.curblk, sizeof(uint8_t), BLOCK_SIZE, fsource);
+  xd3_set_source(&stream, &src);
+  block_cache[0].onblk = src.onblk;
+
+  do {
+    input_read = fread(input_buffer, sizeof(uint8_t), BLOCK_SIZE, fpatch);
+    if (input_read < BLOCK_SIZE) {
+      xd3_set_flags(&stream, XD3_FLUSH);
+    }
+    xd3_avail_input(&stream, input_buffer, input_read);
+  process:
+    switch (xd3_decode_input(&stream)) {
+    case XD3_INPUT:
+      continue;
+    case XD3_OUTPUT:
+      fwrite(stream.next_out, sizeof(uint8_t), stream.avail_out, ftarget);
+      xd3_consume_output(&stream);
+      goto process;
+    case XD3_GETSRCBLK: {
+      for (int i = 0; i < cache_size; i++) {
+        if (block_cache[i].blkno == src.getblkno) {
+          src.onblk = block_cache[i].onblk;
+          src.curblk = block_cache[i].blk;
+          src.curblkno = src.getblkno;
+          cache_remove(&block_cache[i]);
+          cache_add(block_cache_nav.prev, &block_cache_nav, 
&block_cache[i].nav);
+          goto process;
+        }
+      }
+      struct cache* cache_el = cache_pop_front(&block_cache_nav);
+      cache_add(block_cache_nav.prev, &block_cache_nav, &cache_el->nav);
+
+      offset = src.blksize * src.getblkno;
+      fseek(fsource, offset, SEEK_SET);
+      cache_el->onblk = fread(
+          cache_el->blk, sizeof(uint8_t), src.blksize, fsource);
+      cache_el->blkno = src.getblkno;
+
+      src.curblkno = cache_el->blkno;
+      src.onblk = cache_el->onblk;
+      src.curblk = cache_el->blk;
+
+      goto process;
+    }
+    case XD3_GOTHEADER:
+    case XD3_WINSTART:
+    case XD3_WINFINISH:
+      /* no action necessary */
+      Py_BLOCK_THREADS
+      put_progress(queue, stream.total_out - written, stream.total_in - read);
+      written = stream.total_out;
+      read = stream.total_in;
+      Py_UNBLOCK_THREADS
+      goto process;
+    default:
+      Py_BLOCK_THREADS if (stream.msg) {
+        printf("%s\n", stream.msg);
+        fflush(stdout);
+      }
+      PyErr_SetFromErrno(PyExc_MemoryError);
+      Py_UNBLOCK_THREADS goto cleanup;
+    }
+
+  } while (input_read == BLOCK_SIZE);
+  if (xd3_close_stream(&stream)) {
+    Py_BLOCK_THREADS PyErr_SetFromErrno(PyExc_AssertionError);
+    Py_UNBLOCK_THREADS
+  }
+
+cleanup:
+  Py_END_ALLOW_THREADS xd3_free_stream(&stream);
+  if (block_cache) {
+    if (block_cache[0].blk) free(block_cache[0].blk);
+    free(block_cache);
+  }
+
+  if (input_buffer)
+    free(input_buffer);
+  if (fsource)
+    fclose(fsource);
+  if (fpatch)
+    fclose(fpatch);
+  if (ftarget) {
+    fflush(ftarget);
+    fclose(ftarget);
+  }
+
+  Py_RETURN_NONE;
+}
+
+static PyMethodDef methods[] = {
+    {"patch", patch, METH_VARARGS, "Runs a patch on provided files"},
+    {NULL, NULL, 0, NULL}};
+
+static struct PyModuleDef xdelta_def = {PyModuleDef_HEAD_INIT, 
"gogdl_xdelta3", NULL,
+                                        -1, methods};
+
+PyMODINIT_FUNC PyInit_gogdl_xdelta3(void) { return 
PyModule_Create(&xdelta_def); }
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/pyproject.toml 
new/heroic-gogdl-1.2.2/pyproject.toml
--- old/heroic-gogdl-1.2.1/pyproject.toml       2026-02-06 15:17:40.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/pyproject.toml       2026-06-04 17:29:32.000000000 
+0200
@@ -44,10 +44,13 @@
 [tool.setuptools.packages.find]
 include = ["gogdl*"]
 
-[tool.setuptools]
-ext-modules = [
-  {name = "gogdl.xdelta3", sources = ["gogdl/xdelta3.c", 
"xdelta3/xdelta3/xdelta3.c"], include-dirs = ["xdelta3"], extra-compile-args = 
["-DSIZEOF_SIZE_T=8", "-DSIZEOF_UNSIGNED_INT=4", "-DSIZEOF_UNSIGNED_LONG=8", 
"-DSIZEOF_UNSIGNED_LONG_LONG=8", "-DXD3_USE_LARGEFILE64=1", "-DXD3_ENCODER=0", 
"-DSECONDARY_DJW=0", "-DSECONDARY_LZMA=0", "-DSHELL_TESTS=0"]}
-]
+[[tool.setuptools.ext-modules]]
+name = "gogdl_xdelta3"
+sources = ["gogdl_xdelta3.c", "xdelta3/xdelta3/xdelta3.c"]
+include-dirs = ["xdelta3"]
+py-limited-api = true
+extra-compile-args = ["-DPy_LIMITED_API=0x03090000", "-DSIZEOF_SIZE_T=8", 
"-DSIZEOF_UNSIGNED_INT=4", "-DSIZEOF_UNSIGNED_LONG=8", 
"-DSIZEOF_UNSIGNED_LONG_LONG=8", "-DXD3_USE_LARGEFILE64=1", "-DXD3_ENCODER=0", 
"-DSECONDARY_DJW=0", "-DSECONDARY_LZMA=0", "-DSHELL_TESTS=0"]
+
 
 [tool.setuptools.dynamic]
 version = {attr = "gogdl.version"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/scripts/generate_languages.py 
new/heroic-gogdl-1.2.2/scripts/generate_languages.py
--- old/heroic-gogdl-1.2.1/scripts/generate_languages.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/heroic-gogdl-1.2.2/scripts/generate_languages.py        2026-06-04 
17:29:32.000000000 +0200
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+import urllib.request
+import json
+
+# Scipt used to generate entries for LANGUAGES static.
+
+def main():
+    response = urllib.request.urlopen("https://api.gog.com/v1/languages";)
+    data = response.read()
+    languages = json.loads(data)
+
+    for lang in languages["_embedded"]["items"]:
+        code = lang["code"]
+        name = lang["name"]
+        native_name = lang["nativeName"]
+        deprecated_codes = [f"\"{n}\"" for n in lang["deprecatedCodes"]]
+        print("Language(" + '"{}", "{}", "{}", [{}]'.format(
+            code, name, native_name, ",".join(deprecated_codes)) + "),")
+
+
+if __name__ == "__main__":
+    main()
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/heroic-gogdl-1.2.1/zipapp_main.py 
new/heroic-gogdl-1.2.2/zipapp_main.py
--- old/heroic-gogdl-1.2.1/zipapp_main.py       1970-01-01 01:00:00.000000000 
+0100
+++ new/heroic-gogdl-1.2.2/zipapp_main.py       2026-06-04 17:29:32.000000000 
+0200
@@ -0,0 +1,30 @@
+# This is Linux only right now
+
+import os
+import sys
+import zlib
+import zipfile
+
+cache_path = os.path.join(
+        os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")), 
'heroic_gogdl'
+    )
+
+vendored_packages_path = os.path.join(cache_path, 'vendored')
+vendored_packages_lock = os.path.join(cache_path, 'vendored.lock')
+if zipfile.is_zipfile(os.path.dirname(__file__)):
+    with zipfile.ZipFile(os.path.dirname(__file__)) as zf:
+        should_extract = True
+        gogdl_xdelta = os.path.join(vendored_packages_path, 
'gogdl_xdelta3.abi3.so')
+        xdelta = zf.getinfo('gogdl_xdelta3.abi3.so')
+        if os.path.exists(gogdl_xdelta):
+            with open(gogdl_xdelta, 'rb') as f:
+                crc = zlib.crc32(f.read())
+                should_extract = xdelta.CRC != crc
+        if should_extract:
+            extracted = zf.extract(xdelta, vendored_packages_path)
+            extracted = os.chmod(extracted, xdelta.external_attr >> 16)
+
+    sys.path.insert(0, vendored_packages_path)
+
+import gogdl.cli
+gogdl.cli.main()
\ No newline at end of file

++++++ use-system-xdelta3.patch ++++++
--- /var/tmp/diff_new_pack.vCF7nI/_old  2026-06-08 14:24:21.370658916 +0200
+++ /var/tmp/diff_new_pack.vCF7nI/_new  2026-06-08 14:24:21.374659082 +0200
@@ -1,11 +1,11 @@
-diff '--color=auto' -rubN 
heroic-gogdl-1.2.1.orig/gogdl/dl/workers/task_executor.py 
heroic-gogdl-1.2.1/gogdl/dl/workers/task_executor.py
---- heroic-gogdl-1.2.1.orig/gogdl/dl/workers/task_executor.py  2026-02-06 
11:17:40.000000000 -0300
-+++ heroic-gogdl-1.2.1/gogdl/dl/workers/task_executor.py       2026-04-24 
00:16:47.527280540 -0300
+diff '--color=auto' -rub 
heroic-gogdl-1.2.2.orig/gogdl/dl/workers/task_executor.py 
heroic-gogdl-1.2.2/gogdl/dl/workers/task_executor.py
+--- heroic-gogdl-1.2.2.orig/gogdl/dl/workers/task_executor.py  2026-06-04 
12:29:32.000000000 -0300
++++ heroic-gogdl-1.2.2/gogdl/dl/workers/task_executor.py       2026-06-05 
19:32:36.526383447 -0300
 @@ -17,7 +17,8 @@
  from enum import Enum, auto
  from multiprocessing import Process, Queue
  from gogdl.dl.objects.generic import MemorySegment, TaskFlag, TerminateWorker
--import gogdl.xdelta3
+-import gogdl_xdelta3
 +import subprocess
 +import shutil
  
@@ -15,7 +15,7 @@
                      patch = os.path.join(task.destination, task.patch_file)
                      patch = dl_utils.get_case_insensitive_name(patch)
                      target = task_path
--                    gogdl.xdelta3.patch(source, patch, target, 
self.speed_queue)
+-                    gogdl_xdelta3.patch(source, patch, target, 
self.speed_queue)
 +
 +                    xdelta = shutil.which("xdelta3")
 +                    if not xdelta:
@@ -44,4 +44,21 @@
  
                  except Exception as e:
                      print("Patch failed", e)
+diff '--color=auto' -rub heroic-gogdl-1.2.2.orig/pyproject.toml 
heroic-gogdl-1.2.2/pyproject.toml
+--- heroic-gogdl-1.2.2.orig/pyproject.toml     2026-06-04 12:29:32.000000000 
-0300
++++ heroic-gogdl-1.2.2/pyproject.toml  2026-06-05 19:47:32.018917622 -0300
+@@ -44,13 +44,5 @@
+ [tool.setuptools.packages.find]
+ include = ["gogdl*"]
+ 
+-[[tool.setuptools.ext-modules]]
+-name = "gogdl_xdelta3"
+-sources = ["gogdl_xdelta3.c", "xdelta3/xdelta3/xdelta3.c"]
+-include-dirs = ["xdelta3"]
+-py-limited-api = true
+-extra-compile-args = ["-DPy_LIMITED_API=0x03090000", "-DSIZEOF_SIZE_T=8", 
"-DSIZEOF_UNSIGNED_INT=4", "-DSIZEOF_UNSIGNED_LONG=8", 
"-DSIZEOF_UNSIGNED_LONG_LONG=8", "-DXD3_USE_LARGEFILE64=1", "-DXD3_ENCODER=0", 
"-DSECONDARY_DJW=0", "-DSECONDARY_LZMA=0", "-DSHELL_TESTS=0"]
+-
+-
+ [tool.setuptools.dynamic]
+ version = {attr = "gogdl.version"}
 

Reply via email to