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"}
