From 922aea9bc1c8fd67458e7eef52527597e6ef5bb1 Mon Sep 17 00:00:00 2001 From: paste Date: Thu, 25 Jun 2026 21:54:09 -0500 Subject: [PATCH] Add MPQ verify modes, harden CI assertions, fix CRLF/docstring - mpqpack gains --list and --cat (StormLib-backed) for reading archives back - CI now asserts the archive lists the expected file and that version substitution landed inside the MPQ, not just that a file exists - Dockerfile strips CRLF from entrypoint/package so local Windows builds work - drop invalid escape in package.py docstring --- .gitea/workflows/ci.yml | 12 ++++++++++-- .gitignore | 3 +++ Dockerfile | 4 +++- package.py | 2 +- src/mpqpack.c | 43 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 0f68ed3..8eddf98 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -28,8 +28,16 @@ jobs: -v "$PWD/test:/work" \ mpq-packager:ci - - name: Assert artifact exists + - name: Assert archive is valid and contains the expected file run: | set -euo pipefail test -s test/dist/patch-Z.mpq - echo "OK: $(stat -c%s test/dist/patch-Z.mpq) bytes" + echo "size: $(stat -c%s test/dist/patch-Z.mpq) bytes" + # Read the archive back with StormLib (mpqpack --list/--cat). + docker run --rm --entrypoint mpqpack -v "$PWD/test:/work" mpq-packager:ci \ + --list /work/dist/patch-Z.mpq | tee /tmp/list.txt + grep -q 'Interface\\AddOns\\Fixture\\Hello.lua' /tmp/list.txt + docker run --rm --entrypoint mpqpack -v "$PWD/test:/work" mpq-packager:ci \ + --cat /work/dist/patch-Z.mpq 'Interface\AddOns\Fixture\Hello.lua' \ + | grep -q 'VERSION = "v0.0.0-ci"' + echo "OK: archive valid, path + substitution confirmed" diff --git a/.gitignore b/.gitignore index 3b6de2e..8923179 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ dist/ test/dist/ *.mpq *.o +# local validation helpers (not needed in CI) +test/stub_mpqpack.py +test/mpqpack.cmd diff --git a/Dockerfile b/Dockerfile index 68b551b..f0951d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY --from=build /usr/local/bin/mpqpack /usr/local/bin/mpqpack COPY package.py /opt/mpq-packager/package.py COPY entrypoint.sh /opt/mpq-packager/entrypoint.sh -RUN chmod +x /opt/mpq-packager/entrypoint.sh +# Strip any CRLF (Windows checkouts) so the shebang and bash parse correctly. +RUN sed -i 's/\r$//' /opt/mpq-packager/entrypoint.sh /opt/mpq-packager/package.py \ + && chmod +x /opt/mpq-packager/entrypoint.sh ENTRYPOINT ["/opt/mpq-packager/entrypoint.sh"] diff --git a/package.py b/package.py index 098b84b..8d4ac6a 100644 --- a/package.py +++ b/package.py @@ -23,7 +23,7 @@ Manifest schema (all keys optional except `contents`): extensions: [".lua", ".xml", ".toc"] contents: - src: . # path in the repo (file or dir) - dest: Interface/GlueXML # in-MPQ path (forward slashes; converted to \) + dest: Interface/GlueXML # in-MPQ path (forward slashes become backslashes) ignore: # glob patterns, matched against repo-relative paths - ".git/**" - ".gitea/**" diff --git a/src/mpqpack.c b/src/mpqpack.c index cdf8749..07b46a6 100644 --- a/src/mpqpack.c +++ b/src/mpqpack.c @@ -80,9 +80,50 @@ static unsigned walk(const char *dir, int add) { return count; } +/* --list : enumerate archive contents (names + sizes). */ +static int cmd_list(const char *mpq) { + HANDLE h; + if (!SFileOpenArchive(mpq, 0, STREAM_FLAG_READ_ONLY, &h)) die("open", mpq); + SFILE_FIND_DATA fd; + HANDLE find = SFileFindFirstFile(h, "*", &fd, NULL); + if (find) { + do { + printf("%-50s %u\n", fd.cFileName, fd.dwFileSize); + } while (SFileFindNextFile(find, &fd)); + SFileFindClose(find); + } + SFileCloseArchive(h); + return 0; +} + +/* --cat : dump one file's bytes to stdout. */ +static int cmd_cat(const char *mpq, const char *name) { + HANDLE h, f; + if (!SFileOpenArchive(mpq, 0, STREAM_FLAG_READ_ONLY, &h)) die("open", mpq); + if (!SFileOpenFileEx(h, name, 0, &f)) die("openfile", name); + char buf[65536]; + DWORD got; + while (SFileReadFile(f, buf, sizeof buf, &got, NULL) || got) { + fwrite(buf, 1, got, stdout); + if (got < sizeof buf) break; + } + SFileCloseFile(f); + SFileCloseArchive(h); + return 0; +} + int main(int argc, char **argv) { + if (argc >= 3 && !strcmp(argv[1], "--list")) + return cmd_list(argv[2]); + if (argc >= 4 && !strcmp(argv[1], "--cat")) + return cmd_cat(argv[2], argv[3]); + if (argc < 3) { - fprintf(stderr, "usage: %s [maxFiles]\n", argv[0]); + fprintf(stderr, + "usage: %s [maxFiles]\n" + " %s --list \n" + " %s --cat \n", + argv[0], argv[0], argv[0]); return 2; } const char *out = argv[1];