Skip to content

Add support for PlM (Oodle Mermaid) compressed save files#11

Open
jstop01 wants to merge 2 commits intoiebb:masterfrom
jstop01:fix/plm-oodle-support
Open

Add support for PlM (Oodle Mermaid) compressed save files#11
jstop01 wants to merge 2 commits intoiebb:masterfrom
jstop01:fix/plm-oodle-support

Conversation

@jstop01
Copy link
Copy Markdown

@jstop01 jstop01 commented Mar 26, 2026

Summary

Palworld v0.6+ changed the save file compression format from PlZ (zlib) to PlM (Oodle Mermaid). This broke all file imports on palworld.tf, causing the "Is it really a Palworld Save?" error for every drag-and-drop attempt.

This PR adds full support for the new PlM format while maintaining backward compatibility with older PlZ saves.

Fixes #8, #9, #10

What Changed

PlM (Oodle) Decompression Support (src/libs/save.js)

  • Detects the compression format by reading the magic bytes in the file header (PlZ vs PlM)
  • PlM files are decompressed using the ooz Oodle decompressor (compiled to WASM)
  • PlZ files continue to use the existing zlib (pako) decompression
  • On save/export, PlM files are re-compressed as PlZ (double zlib) since Oodle compression is proprietary and not available in ooz-wasm. Palworld can read both formats, so this is fully compatible.

Custom Oodle WASM Loader (src/libs/oozLoader.js)

  • The ooz-wasm npm package uses Emscripten, which is incompatible with webpack/CRA's module resolution (new URL('./', import.meta.url) fails)
  • This file manually loads the Oodle WASM binary from public/ooz.wasm and exposes the Kraken_Decompress function
  • Import mapping derived from the Emscripten glue code: a.a = emscripten_resize_heap, a.b = emscripten_memcpy_js

Updated uesave-rs (rust/Cargo.toml, rust/src/lib.rs)

  • Switched dependency from iebb/uesave-rs (pinned at Jan 2024) to upstream trumank/uesave-rs which includes:
    • Fix for PackageVersion::New serialization (the old version panicked on write for v0.6+ saves)
    • Fix for missing property tag array index on serialization
    • Many other serialization improvements
  • Rewrote rust/src/lib.rs to use the new SaveReader API with error_to_raw(true) for resilient parsing
  • Changed from panic!() to proper Result<T, JsValue> error handling (no more "unreachable" WASM errors)
  • deserialize() now accepts &[u8] directly instead of a custom Buffer JS type
  • Pre-built WASM artifacts included in src/libs/uesave/ (built with wasm-pack build rust --target web)

Raw JSON Preservation (src/libs/save.js, src/components/RawEditor.js)

  • The raw JSON string from deserialize() is now preserved and passed through to writeFile()
  • This prevents type coercion issues where the tree editor converts string values like "1234" to integers 1234, which would cause serde deserialization errors on save
  • Falls back to LosslessJSON.stringify(gvas) if rawJson is not available

Webpack Configuration (config-overrides.js)

  • Added topLevelAwait: true experiment for async WASM initialization
  • Added Node.js module fallbacks (module, path, fs, url, crypto) for browser builds
  • Wrapped git rev-parse in try/catch so the build works outside of git repos

Binary Files

File Size Description
public/ooz.wasm ~90KB Oodle decompressor (extracted from ooz-wasm npm package)
public/uesave_wasm_bg.wasm ~580KB uesave GVAS parser/writer
src/libs/uesave/uesave_wasm_bg.wasm ~580KB Same file, used by wasm-pack JS bindings

How to Rebuild

# Install Rust and wasm-pack (if not installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
cargo install wasm-pack

# Build uesave WASM module
npm run build-rust    # or: wasm-pack build rust --out-dir ../src/libs/uesave --target web

# Copy WASM to public directory
cp src/libs/uesave/uesave_wasm_bg.wasm public/

# Install dependencies and start
npm install
npm start

Test Plan

  • Load a PlM format save file (Palworld v0.6+) via drag-and-drop
  • Load a PlZ format save file (older Palworld versions) via drag-and-drop
  • Edit values in the JSON tree editor
  • Export as .sav file (Save button)
  • Export as .json file
  • Verify exported .sav can be loaded back into the editor
  • Verify exported .sav works in Palworld (needs game testing)

🤖 Generated with Claude Code

jstop01 and others added 2 commits March 26, 2026 11:10
Palworld v0.6+ changed the save file compression from PlZ (zlib) to
PlM (Oodle Mermaid). This caused all file imports to fail with
"Is it really a Palworld Save?" on both palworld.tf and local builds.

Changes:
- Detect PlM magic bytes and decompress using Oodle (ooz-wasm)
- Add custom oozLoader.js to bypass Emscripten/webpack incompatibility
- Update uesave-rs to upstream (trumank/uesave-rs) for serialization fixes
- Rewrite rust/src/lib.rs to use new SaveReader API with proper error handling
- Preserve raw JSON from deserialize to avoid type coercion on save
- PlM saves are written back as PlZ (zlib) since Oodle compression
  is not available in ooz-wasm (Palworld reads both formats)
- Add topLevelAwait webpack experiment and Node.js module fallbacks
- Handle missing git repo gracefully in config-overrides.js

Fixes iebb#8, iebb#9, iebb#10

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The tree editor (vanilla-jsoneditor) converts string values like "1234"
to integers and LosslessNumber objects to arrays/objects, breaking
serde deserialization on save.

Add preserveTypes() that recursively compares original and edited
property trees, restoring original types (string, LosslessNumber)
when the tree editor mangles them. Uses rawJson from initial
deserialize as the type-correct reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

cant import save files

1 participant