Skip to main content
The ModPackageManager class handles all operations related to ZZAR mod packages, including installation, removal, enabling/disabling, load order management, and conflict resolution.

Constructor

from src.mod_package_manager import ModPackageManager

manager = ModPackageManager(
    mod_library_path=None,
    persistent_mod_manager=None
)
mod_library_path
str | Path
default:"None"
Custom path to mod library directory. If None, uses the default from config_manager.
persistent_mod_manager
PersistentModManager
default:"None"
Optional manager for tracking persistent mod changes.

Methods

validate_mod_package

Validates a .zzar package file structure and metadata.
metadata = manager.validate_mod_package(zzar_path)
zzar_path
str | Path
required
Path to the .zzar file to validate.
metadata
dict
The mod’s metadata dictionary containing:
  • name (str): Mod name
  • author (str): Mod author
  • version (str): Mod version
  • replacements (dict): PCK replacements mapping
  • format_version (str): Package format version
Raises:
  • InvalidModPackageError: If the package is invalid or corrupted

install_mod

Installs a .zzar mod package into the mod library.
result = manager.install_mod(zzar_path)
zzar_path
str | Path
required
Path to the .zzar package to install.
result
dict
Installation result containing:
  • uuid (str): Unique identifier for the installed mod
  • replaced (bool): Whether this replaced an existing version
  • mod_name (str): Name of the mod
  • version (str): Version installed
Behavior:
  • Automatically replaces older versions of the same mod
  • Returns None if trying to install an older version over a newer one
  • Extracts mod to mod_library/mods/{uuid}/
  • Adds mod to config but keeps it disabled by default

get_installed_mods

Returns a list of all installed mods in load order.
mods = manager.get_installed_mods()
mods
list[dict]
List of mod dictionaries, each containing:
  • uuid (str): Mod unique identifier
  • enabled (bool): Whether mod is enabled
  • priority (int): Load order priority (0 = highest)
  • metadata (dict): Mod metadata from metadata.json
  • install_date (str): ISO format installation timestamp
  • thumbnail_path (Path): Path to thumbnail image if available

set_mod_enabled

Enables or disables a mod.
manager.set_mod_enabled(mod_uuid, enabled=True)
mod_uuid
str
required
UUID of the mod to enable/disable.
enabled
bool
required
True to enable, False to disable.

remove_mod

Completely removes a mod from the library.
manager.remove_mod(mod_uuid)
mod_uuid
str
required
UUID of the mod to remove.
Note: This deletes the mod files permanently.

update_load_order

Updates the load order priority of mods.
manager.update_load_order(ordered_uuids)
ordered_uuids
list[str]
required
List of mod UUIDs in desired load order (first = highest priority).
Raises:
  • ValueError: If any UUID is not a valid installed mod

resolve_conflicts

Resolves file conflicts between enabled mods based on load order.
resolved = manager.resolve_conflicts(preferences=None)
preferences
dict
default:"None"
Optional conflict preferences. Keys are "pck_name:file_id", values are mod names.
resolved
dict
Nested dictionary: {pck_name: {file_id: replacement_info}}Each replacement_info contains:
  • wem_path (str): Path to replacement WEM file
  • mod_uuid (str): UUID of winning mod
  • mod_name (str): Name of winning mod
  • lang_id (int): Language ID
  • bnk_id (int): BNK ID if inside soundbank
  • file_type (str): File type (“wem”)
  • sound_name (str): Sound name if available
  • conflicts_with (list): UUIDs of losing mods
Behavior:
  • Higher priority mods (earlier in load order) win conflicts
  • Automatically removes orphaned mods from config

get_conflicts_summary

Returns a summary of all conflicts.
summary = manager.get_conflicts_summary()
summary
dict
  • total_replacements (int): Total number of file replacements
  • affected_pcks (list[str]): List of PCK names being modified
  • conflicts (list[dict]): List of individual conflicts

apply_mods

Applies all enabled mods to the game audio directory.
manager.apply_mods(
    game_audio_dir,
    persistent_audio_dir,
    progress_callback=None,
    conflict_preferences=None
)
game_audio_dir
str | Path
required
Path to the game’s Audio directory.
persistent_audio_dir
str | Path
required
Path to Persistent Audio directory for modified PCKs.
progress_callback
callable
default:"None"
Function called with (message, current, total) for progress updates.
conflict_preferences
dict
default:"None"
Conflict resolution preferences.
Raises:
  • ModApplicationError: If application fails
Behavior:
  • Creates modified PCK files in persistent_audio_dir
  • Sets modified PCKs to read-only (0o444)
  • Removes PCKs from disabled mods
  • Supports both direct WEM replacement and BNK-embedded WEMs

create_mod_package

Creates a new .zzar mod package from replacements.
package_path = manager.create_mod_package(
    output_path,
    metadata,
    current_replacements,
    thumbnail_path=None
)
output_path
str | Path
required
Where to save the .zzar package.
metadata
dict
required
Metadata dictionary with keys: name, author, version, description.
current_replacements
dict
required
Replacements dictionary from resolve_conflicts() or similar.
thumbnail_path
str | Path
default:"None"
Optional path to thumbnail image (converted to PNG).
package_path
Path
Path to the created .zzar package.

Exceptions

InvalidModPackageError

Raised when a .zzar package is invalid or corrupted.
from src.mod_package_manager import InvalidModPackageError

ModApplicationError

Raised when mod application fails.
from src.mod_package_manager import ModApplicationError

Example Usage

from src.mod_package_manager import ModPackageManager
from pathlib import Path

# Initialize manager
manager = ModPackageManager()

# Install a mod
result = manager.install_mod("character_voice_mod.zzar")
if result:
    print(f"Installed {result['mod_name']} v{result['version']}")
    
    # Enable the mod
    manager.set_mod_enabled(result['uuid'], True)

# Check for conflicts
summary = manager.get_conflicts_summary()
print(f"Total replacements: {summary['total_replacements']}")
print(f"Conflicts: {len(summary['conflicts'])}")

# Apply mods to game
def progress(msg, current, total):
    print(f"[{current}/{total}] {msg}")

manager.apply_mods(
    game_audio_dir="/path/to/game/Audio",
    persistent_audio_dir="/path/to/game/Persistent/Audio",
    progress_callback=progress
)

Configuration Files

The manager uses these configuration files:
  • mod_config.json: Stores installed mods and load order
    • Location: ~/.config/ZZAR/mod_config.json (Linux) or %APPDATA%/ZZAR/mod_config.json (Windows)
    • Structure: {"installed_mods": {}, "load_order": []}
  • Mod library: Stores extracted mod files
    • Location: ~/.local/share/ZZAR/mod_library/mods/ (Linux) or %LOCALAPPDATA%/ZZAR/mod_library/mods/ (Windows)
    • Each mod in subdirectory named by UUID

Build docs developers (and LLMs) love