Skip to content

Migrate from libmpq to mpqfs#8482

Merged
AJenbo merged 5 commits intomasterfrom
mpqfs
Mar 29, 2026
Merged

Migrate from libmpq to mpqfs#8482
AJenbo merged 5 commits intomasterfrom
mpqfs

Conversation

@AJenbo
Copy link
Copy Markdown
Member

@AJenbo AJenbo commented Feb 25, 2026

mpqfs is a minimal MIT-licensed MPQ v1 reader/writer that replaces both libmpq and PKWare in a single dependency.

We use mpqfs from: https://github.com/diasurgical/mpqfs

Impact:

  • DevilutionX is now 2,149 lines lighter (25 files changed, 184 insertions, 2,333 deletions, mpqfs is 2643 lines and libmpq is ~2500).
  • The entire block-level SDL_RWops streaming implementation (~300 lines of manual seek/read/block management) is replaced by a single mpqfs_open_rwops() call.
  • File-number plumbing removed throughout, mpqfs uses filenames directly, simplifying AssetRef, FindAsset, and all call sites.
  • The callback-based PKWare implode()/explode() API is replaced with simple buffer-to-buffer calls.
  • Thread-safe audio streaming is now a single function call instead of manual archive cloning + block offset table management.

P.s. despite the angry CI this does work on my machine ™️

image

I need some sleep before I can figure out the CI issue.

P.s.s. should I strip out the write feature or expand it and also replace that part of DevilutionX with mpqfs?

Update: Builds should work now, the intergration test hash is updated since the different compression algo produces a different but valid data stream.

@AJenbo AJenbo force-pushed the mpqfs branch 5 times, most recently from e80f3db to 09ab7c7 Compare February 26, 2026 03:47
@glebm
Copy link
Copy Markdown
Collaborator

glebm commented Feb 26, 2026

Amazing! How is the performance?

libmpq is pretty fast and reuses the decompression buffer (per thread).

File-number plumbing removed throughout

Some of the file-number stuff was intentional to avoid computing the number multiple times, though I'm not sure how important that micro-optimization really is.

@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Feb 26, 2026

Amazing! How is the performance?

libmpq is pretty fast and reuses the decompression buffer (per thread).

File-number plumbing removed throughout

Some of the file-number stuff was intentional to avoid computing the number multiple times, though I'm not sure how important that micro-optimization really is.

Yeah from what I saw it should make very little difference, I could look into reintroducing it, but it would probably make the interface a lot less clean

@AJenbo AJenbo force-pushed the mpqfs branch 2 times, most recently from 7a5fe9e to 8842910 Compare February 27, 2026 13:07
@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Feb 27, 2026

@glebm applied some optimizations to mpqfs (reuse compression buffer) and things should be ready for review now.

@glebm
Copy link
Copy Markdown
Collaborator

glebm commented Feb 27, 2026

Yeah from what I saw it should make very little difference, I could look into reintroducing it, but it would probably make the interface a lot less clean

I think it'd just require adding a has and open functions that accept a file number?

MPQFS_API bool mpqfs_has_file_hash(mpqfs_archive_t *archive, uint32_t hash);
MPQFS_API SDL_IOStream *mpqfs_open_io_from_hash(mpqfs_archive_t *archive, uint32_t hash);
// etc...

@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Feb 28, 2026

Alright I think I got the optimizations working

@AJenbo AJenbo requested a review from glebm February 28, 2026 21:52
@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Feb 28, 2026

I'm a little unsure about disabeling compression support on OG Xbox, but hopefully it won't need it for the base game.

Comment thread CMake/platforms/xbox_nxdk.cmake
@glebm
Copy link
Copy Markdown
Collaborator

glebm commented Mar 1, 2026

Can you please also update:

@glebm
Copy link
Copy Markdown
Collaborator

glebm commented Mar 1, 2026

Looking at the writer implementation in mpqfs, it appears to keep all of the archive contents in RAM, which can increase peak RAM usage (save files can be > 1 MiB).

DevilutionX implementation is leaner -- it writes the files directly to the archive as they get added.

@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Mar 1, 2026

Yeah mpqfs write implementation is currently just good enough to produce valid saves, but not good enough to be used. I was wondering if I should just strip it out but ideally I would prefer to move all MPQ support to mpqfs rather then having an internal implementation for writing like we currently do.

I'll see about fixing compression on Xbox and making the write support ready to replace ours. Thanks for going over the code.

@glebm
Copy link
Copy Markdown
Collaborator

glebm commented Mar 1, 2026

Makes sense. I also think that it might be cleaner to remove the SDL stuff from mpqfs.

This is because multiple SDL versions are supported, which might necessitate multiple mpqfs packages (mpqfs-sdl2, mpqfs-sdl3, mpqfs-no-sdl, etc), if mpqfs is ever used by projects other than DevilutionX.

@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Mar 1, 2026

Yeah I did consider that might not be ideal if it was to be used by a save game viewer. I didn't realize it could also affect how packaging works. The intent was to make it's interface to us as simple as possible.

@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Mar 23, 2026

Amazing! How is the performance?

CPU Results (spawn.mpq, Release build)

Operation libmpq mpqfs Speedup
hash_string (×4) 0.080 µs 0.058 µs 1.37×
file_hash (triple) 0.054 µs 0.039 µs 1.38×
encrypt+decrypt 4K 2.339 µs 2.319 µs 1.01×
encrypt+decrypt 64K 37.629 µs 37.481 µs 1.00×
open+close 27.1 µs 26.5 µs 1.02×
file lookup 0.047 µs 0.037 µs 1.28×
file read 312 µs 313 µs 1.00×
bulk read (1028 files) 164 ms 165 ms 1.00×

Memory Results

Metric libmpq mpqfs Improvement
Total on open 66.3 KiB 48.4 KiB 27% less
Per-file transient overhead ~91 KiB ~8 KiB 11× less
Bulk extraction peak RSS delta 1.37 MiB 776 KiB 44% less
Memory scaling (65K entries) 3200 KiB 2048 KiB 1.56×

@AJenbo AJenbo force-pushed the mpqfs branch 3 times, most recently from 048c041 to 0f8eab5 Compare March 23, 2026 23:15
@AJenbo
Copy link
Copy Markdown
Member Author

AJenbo commented Mar 23, 2026

  • mpqfs now supports streaming data.
  • mpqfs is now used for write operations as well as read.
  • fix xbox compression
  • mkdist has been updated
  • Compression and decompression now use stack buffers (~8 KB) to avoid heap allocations on the hot path, falling back to heap for larger payloads.
  • When a save file is fully rewritten it rotates a .tmp, this should be safer then what we did before.

Comment thread Source/mpq/mpq_writer.cpp
Comment thread Source/pfile.cpp Outdated
StephenCWills
StephenCWills previously approved these changes Mar 25, 2026
@AJenbo AJenbo merged commit 6f31997 into master Mar 29, 2026
25 checks passed
@AJenbo AJenbo deleted the mpqfs branch March 29, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants