Skip to content

inwc3/JMPQ3

Repository files navigation

CircleCI Jit Coverage Status codebeat badge

JMPQ3

JMPQ3 is a small Java library for reading and modifying MPQ (MoPaQ) archives. Common file endings are .mpq, .w3m, and .w3x.

MoPaQ is Blizzard's older proprietary archive format for game data. It is used by Warcraft III maps and was later replaced by CASC in newer Blizzard games.

JMPQ3 is primarily tested with Warcraft III maps and MPQs. Archives from other games may work, but Warcraft III compatibility is the main target.

For MPQ format background and a graphical editor, see Ladislav Zezula's MPQ tools: http://www.zezula.net/en/mpq/main.html

Requirements

JMPQ3 1.9.0 and newer requires Java 11 or newer at runtime.

The project currently builds with a Java 17 toolchain while compiling Java 11-compatible bytecode.

Installation

JMPQ3 is available through JitPack: https://jitpack.io/#inwc3/JMPQ3/

Gradle:

repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.inwc3:JMPQ3:1.9.8'
}

You can also depend on a Git commit or branch through JitPack while testing unreleased changes.

Opening Archives

Use try-with-resources so the editor is closed correctly. Writable archives are rebuilt when the editor is closed.

import systems.crigges.jmpq3.JMpqEditor;
import systems.crigges.jmpq3.MPQOpenOption;

import java.io.File;
import java.nio.charset.StandardCharsets;

try (JMpqEditor mpq = new JMpqEditor(new File("MyMap.w3x"), MPQOpenOption.READ_ONLY, MPQOpenOption.FORCE_V0)) {
    if (mpq.hasFile("war3map.j")) {
        byte[] script = mpq.extractFileAsBytes("war3map.j");
        System.out.println(new String(script, StandardCharsets.UTF_8));
    }

    for (String fileName : mpq.getFileNames()) {
        System.out.println(fileName);
    }
}

MPQOpenOption.READ_ONLY opens the archive without modifying it.

MPQOpenOption.FORCE_V0 reads the archive like Warcraft III does, ignoring newer optional MPQ metadata where needed. This is useful for Warcraft III maps and some intentionally odd or damaged archives.

Modifying Archives

Open without READ_ONLY to allow writes. Changes are applied when close() runs.

import systems.crigges.jmpq3.JMpqEditor;
import systems.crigges.jmpq3.MPQOpenOption;

import java.io.File;

try (JMpqEditor mpq = new JMpqEditor(new File("MyMap.w3x"), MPQOpenOption.FORCE_V0)) {
    if (mpq.hasFile("war3map.j")) {
        mpq.deleteFile("war3map.j");
    }

    mpq.insertFile("war3map.j", new File("build/war3map.j"));
    mpq.insertByteArray("war3mapImported/readme.txt", "generated by JMPQ3".getBytes());
}

insertFile stores the file path and reads the file when the archive is rebuilt on close. Keep that source file alive until the editor is closed. If the data is temporary, use insertByteArray instead.

To overwrite an existing file directly:

mpq.insertFile("war3map.j", new File("build/war3map.j"), true);
mpq.insertByteArray("war3mapImported/readme.txt", bytes, true);

Creating Archives From Scratch

JMPQ3 can create a minimal empty archive and then rebuild it with inserted files. This is useful for Warcraft III "folder mode" maps, where a .w3x directory contains the files that should become a real MPQ archive.

import systems.crigges.jmpq3.JMpqEditor;
import systems.crigges.jmpq3.MPQOpenOption;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;

File outputMap = new File("build/MyMap.w3x");
JMpqEditor.createEmptyArchive(outputMap);

try (JMpqEditor mpq = new JMpqEditor(outputMap, MPQOpenOption.FORCE_V0)) {
    mpq.insertFile("war3map.w3i", new File("folderMap/war3map.w3i"));
    mpq.insertFile("war3map.j", new File("folderMap/war3map.j"));
    mpq.insertFile("war3mapImported/icon.blp", new File("folderMap/war3mapImported/icon.blp"));

    byte[] generatedScript = Files.readAllBytes(Path.of("build/war3map.j"));
    mpq.insertByteArray("war3map.j", generatedScript, true);
}

You can also get the initial empty archive bytes without writing a file:

byte[] emptyArchive = JMpqEditor.createEmptyArchive();

Empty directories are not represented in MPQ archives, so only insert regular files.

Extracting All Known Files

MPQs do not always contain a complete list of filenames. extractAllFiles extracts known files when a usable (listfile) is available. Archives without a complete listfile may still contain files that can only be accessed if you know their exact path.

try (JMpqEditor mpq = new JMpqEditor(new File("MyMap.w3x"), MPQOpenOption.READ_ONLY, MPQOpenOption.FORCE_V0)) {
    mpq.extractAllFiles(new File("extracted"));
}

For writable archives with a missing or incomplete listfile, you can provide an external listfile before rebuilding:

try (JMpqEditor mpq = new JMpqEditor(new File("MyMap.w3x"), MPQOpenOption.FORCE_V0)) {
    mpq.setExternalListfile(new File("listfile.txt"));
    mpq.insertByteArray("war3mapImported/generated.txt", "hello".getBytes());
}

Known Issues

  • Unsupported decompression algorithms: sparse and bzip2.
  • Supported compression is zlib/zopfli.
  • JMPQ3 does not currently build a valid (attributes) file. Warcraft III maps appear to work without it.
  • Empty directories are not stored in MPQ archives.

About

Native Java mpq archive library

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors