dream-ini 0.1.0

Import Morrowind.ini settings into OpenMW configuration files
Documentation

dream-ini

dream-ini imports settings from Morrowind.ini into an openmw.cfg-style file. It is a standalone Rust importer compatible with OpenMW's Morrowind.ini import needs, with deliberate UX improvements over the original C++ tool.

Build

Desktop builds use the default feature set, which includes the native GUI and the CLI:

cargo build --release

Release platform support is intentionally split by build shape:

Platform Release build shape Availability
Linux x64, Windows x64, macOS x64/ARM64 default features Desktop GUI and CLI
PortMaster ARM64 --no-default-features --features portmaster-gui Framebuffer GUI and CLI
Android ARM64 --no-default-features CLI/importer only

Other targets may build the library or CLI, but GUI, launcher installation, and controller support are only promised for the platform rows above.

Usage

dream-ini --ini <FILE> [--cfg <FILE>] [--output <FILE>|--in-place] [options]

--ini is required for imports. By default, the imported cfg text is written to stdout and diagnostics go to stderr, so shell redirection is safe. Use --output to write a separate cfg file or --in-place with --cfg to update the base cfg. If --cfg is provided, it is read first and intentionally imported keys are replaced. In-place and same-directory output preserve unrelated comments, entries, chain controls, and relative/token path spelling through openmw-config's preservation-oriented serializer. Relocated --cfg + --output writes a resolved export so relative paths do not silently change meaning. If --cfg is omitted, import starts from an empty config.

Import paths are flag-based. Positional Morrowind.ini or openmw.cfg arguments are intentionally unsupported; use --ini, optional --cfg, and optional --output or --in-place.

dream-ini --ini Morrowind.ini > openmw.cfg
dream-ini --ini Morrowind.ini --cfg openmw.cfg > preview.cfg
dream-ini --ini Morrowind.ini --cfg openmw.cfg --in-place
dream-ini --ini Morrowind.ini --output imported.cfg
dream-ini --ini Morrowind.ini --cfg openmw.cfg --output imported.cfg
dream-ini --ini Morrowind.ini --cfg openmw.cfg --game-files --in-place
dream-ini --ini Morrowind.ini --game-files --data "/games/Morrowind/Data Files" > openmw.cfg
dream-ini --ini Morrowind.ini --cfg openmw.cfg --game-files --verbose --in-place
dream-ini --ini Morrowind.ini --cfg openmw.cfg --fonts --encoding win1252 --in-place
dream-ini --ini Morrowind.ini --cfg openmw.cfg -l local-data -r /usr/share/openmw/resources -u user-data --in-place
dream-ini --ini Morrowind.ini --cfg openmw.cfg --no-archives --in-place
dream-ini -C bash > dream-ini.bash
dream-ini -M > dream-ini.1

GUI

When built with the default gui feature, running dream-ini with no arguments opens the desktop graphical importer in a native window. The GUI uses the same explicit import model as the CLI: choose Morrowind.ini, optionally choose an existing openmw.cfg, pick import options, then either preview, save as a separate cfg, or update the existing cfg.

PortMaster builds use the separate portmaster-gui feature with default features disabled. That build draws the same importer UI directly to the Linux framebuffer instead of using desktop windowing. Clipboard support is unavailable in the PortMaster shell. Diagnostic logs, when enabled by the launcher or environment, are written by the launcher/script beside the executable or to the configured log path.

Controller navigation is available in the desktop GUI on Linux, Windows, and macOS, and in the PortMaster framebuffer GUI:

  • D-pad or left stick: move between fields, picker entries, and result tabs.
  • A / South: activate the selected field, toggle checkboxes, open directories, or choose files.
  • B / East or Select: cancel the picker; on the main form it exits the GUI.
  • X / West: clear the selected path field.
  • Start: import from the main form, or choose the current/expected picker path.
  • Left / Right: adjust options; in the picker, Left enters the parent directory and Right enters the selected directory or chooses the selected file.
  • LB / left shoulder: toggle hidden directories in the picker. This is useful for OpenMW paths under ~/.config/openmw or ~/.local/share/openmw.
  • RB / right shoulder: page down through the generated cfg preview.
  • Right stick: scroll the generated cfg preview vertically and horizontally.

On desktop Linux and PortMaster, controller support reads /dev/input event devices directly. If your desktop session does not grant read permission for those devices, the GUI will still run but controller navigation will not appear. Windows and macOS use gilrs for controller input. PortMaster uses the Linux controller backend with a handheld-specific menu/trigger remap for Anbernic-style evdev button codes; the on-screen help shows the behavior used by that build.

Path reminder: Data Files directory is the Morrowind content/archive search path used during import. Classic Morrowind usually points at one Data Files folder; OpenMW can later use many data= directories. data-local, resources, and user-data are OpenMW cfg singleton outputs; they are not used as importer search paths.

Options

  • -c, --cfg <FILE>: optional openmw.cfg input/base path. It is only overwritten when --in-place is supplied.
  • -d, --data <DIR>: explicit Data Files directory searched before cfg/default data paths. Relative paths are resolved from the output cfg directory, from --cfg for stdout preview, or from the current directory and written absolute when stdout has no cfg context.
  • -l, --data-local <DIR>: set the singleton data-local cfg key, replacing any existing value. The value is written as supplied and is not used as an importer search path.
  • -e, --encoding <ENCODING>: character encoding for imported content-file names; win1250, win1251, or win1252.
  • -f, --fonts: import bitmap font fallback settings.
  • -g, --game-files: import .esm and .esp content files.
  • -h, --help: print help.
  • -i, --ini <FILE>: Morrowind.ini input path.
  • -n, --no-archives: disable BSA archive import.
  • -r, --resources <DIR>: set the singleton resources cfg key, replacing any existing value. The value is written as supplied.
  • -u, --user-data <DIR>: set the singleton user-data cfg key, replacing any existing value. The value is written as supplied; this is OpenMW's saves/screenshots/navmesh-cache location, not a mod data directory.
  • -v, --verbose: print content-file timestamp messages during --game-files import.
  • -C, --generate-completion <SHELL>: write a completion script for bash, zsh, fish, powershell, or elvish to stdout.
  • -w, --in-place: update --cfg in place. Requires --cfg and conflicts with --output.
  • -M, --generate-manpage: write a roff manpage to stdout.
  • -o, --output <FILE>: output cfg path.
  • -V, --version: print version information.

Commands

  • install-launcher: install a desktop launcher and icon for the current user. This is intended for Linux and Windows desktop builds. On Linux this writes a .desktop file and hicolor PNG icon to $XDG_DATA_HOME when it is absolute, otherwise to ~/.local/share. On Windows this writes a Start Menu shortcut and .ico icon under %APPDATA%. Non-Linux/non-Windows targets, including macOS and Android, return an unsupported-platform error. PortMaster is a Linux build, but launcher installation is not part of the supported PortMaster release flow.

Behavior

  • Existing cfg output is updated through openmw-config's preservation-oriented serialization when the output remains in the same cfg directory. Comments, unrelated entries, and relative/token path spelling are preserved unless a key is intentionally replaced by the import.
  • Existing cfg output written to a different directory uses resolved flattened serialization so relative paths keep their resolved meaning instead of becoming relative to the new output directory.
  • Output generated without --cfg is new openmw.cfg text built from imported/authored values. It has no source comments or formatting to preserve.
  • When no --output or --in-place mode is selected, cfg text is written to stdout. Diagnostics are written to stderr in stdout mode.
  • Missing cfg files are treated as empty configs and are not created unless they are also the --output path or --in-place target.
  • Omitting cfg starts from an empty config.
  • Missing INI files fail with shell exit code 253, matching the C++ importer's return -3 behavior.
  • Existing cfg entries are preserved unless replaced by imported keys such as encoding, no-sound, fallback, fallback-archive, or content, or by explicit singleton path options such as --data-local, --resources, and --user-data.
  • Content-file and fallback-archive import searches explicit --data paths first, then existing data cfg paths, then <Morrowind.ini parent>/Data Files as a fallback. data-local is OpenMW's highest-precedence runtime data directory, but this importer treats it as an output-only singleton rather than a Morrowind.ini content/archive source. Every .esm/.esp and .bsa entry imported from the INI must be found or the import fails. Any used explicit or fallback data directory is written as data=... if an equivalent data entry is not already present.
  • Relative --data with --output, --cfg, or --in-place is interpreted relative to that cfg context and written as supplied. Relative --data with stdout and no --cfg is interpreted relative to the current directory and written as an absolute path.
  • Explicit singleton options (--data-local, --resources, and --user-data) are output-only and are applied after content/archive resolution. Use --data to add an importer search path.
  • Directory-valued keys read from an existing cfg are interpreted by openmw-config for filesystem lookup. Their authored spelling is not rewritten for normal cfg output.
  • Config, Lua, and event path values are UTF-8 text. Non-UTF-8 operating-system paths are outside the supported API contract and may be represented lossy when converted for cfg/Lua output.

Deliberate Differences From OpenMW's C++ Importer

  • Warnings are written to stderr instead of stdout.
  • Game-file import requires filenames ending in .esm or .esp; the C++ importer accepts any suffix ending in esm or esp.
  • Unreadable input files are reported as errors instead of silently importing from an empty stream.
  • Game-file timestamp sorting uses Rust's full SystemTime precision instead of C++ time_t seconds.
  • --verbose gates content-file timestamp messages. The C++ importer accepts --verbose but prints those messages unconditionally during game-file import.

Lua API

Enable the optional Lua embedding API with the lua feature. It uses mlua with vendored LuaJIT 2.1 in Lua 5.2 compatibility mode.

cargo test --features lua

The crate does not build a Lua require module or cdylib. Lua support is embedding-only; embedders create or register the API table explicitly:

let lua = mlua::Lua::new();
let module = dream_ini::lua::create_module(&lua)?;
lua.globals().set("dream_ini", module)?;

Lua usage:

local result = dream_ini.import_paths({
  ini = "Morrowind.ini",
  cfg = "openmw.cfg",
  game_files = true,
  archives = true,
  fonts = false,
  data_dirs = { "/games/Morrowind/Data Files" },
  user_data = "/home/user/.local/share/openmw",
  encoding = "win1252",
})

print(result.text)
for _, warning in ipairs(result.warnings) do
  print(warning.message)
end
for _, event in ipairs(result.events) do
  if event.kind == "content_file_resolved" then
    print(event.path, event.modified)
  elseif event.kind == "data_dir_added_for_content" then
    print(event.path)
  elseif event.kind == "archive_resolved" then
    print(event.path)
  elseif event.kind == "data_dir_added_for_archive" then
    print(event.path)
  end
end

For import_paths, relative data_dirs are resolved from the cfg file's directory when cfg is supplied. cfg_dir is primarily for import_maps, where there is no cfg path to provide that context; for import_paths without cfg, cfg_dir supplies the cfg context.

Available functions:

  • parse_ini(text, opts): parses a Morrowind INI byte string and returns { entries = multimap, warnings = { ... } }.
  • parse_cfg(text): parses OpenMW cfg text and returns a multimap.
  • serialize_cfg(multimap): serializes a multimap to normalized cfg text.
  • import_maps(cfg, ini, opts): imports parsed multimap data and returns { cfg = multimap, text = string, warnings = { ... }, events = { ... } }.
  • import_paths(opts): imports from opts.ini and optional opts.cfg, returning the same result shape as import_maps.

Warnings are returned in the warnings array. Hard failures such as missing required options, unsupported encodings, malformed multimap input, missing files, or failed imports are raised as Lua errors.

Import events are structured tables. Current event kinds are:

  • { kind = "content_file_resolved", path = string, modified = unix_seconds }
  • { kind = "data_dir_added_for_content", path = string }
  • { kind = "archive_resolved", path = string }
  • { kind = "data_dir_added_for_archive", path = string }

Import warnings are structured tables with a formatted message. Current warning kinds are:

  • { kind = "ignored_empty_value", key = string, message = string }
  • { kind = "malformed_ini_line", line = string, message = string }

Multimaps are represented as key -> array of strings to preserve duplicate keys:

{
  encoding = { "win1252" },
  content = { "Morrowind.esm", "Tribunal.esm" },
}

Rust API

Generate crate documentation with:

cargo doc --open

The library exposes the same multimap model used by the CLI and Lua API. Start with IniImporter, ImportOptions, ImportEvent, ImportWarning, parse_cfg_str, parse_ini_bytes_with_warnings, and serialize_cfg. Path values serialized into cfg text, Lua tables, or import events are UTF-8 strings.

Development

cargo fmt --check
cargo clippy --all-targets -- -W clippy::pedantic -D warnings
cargo test
cargo bench

Lua feature checks:

cargo clippy --all-targets --features lua -- -W clippy::pedantic -D warnings
cargo test --features lua
cargo bench --no-run --features lua

The Criterion benchmark measures a large synthetic parse/import/serialize round trip. It does not include plugin header IO from --game-files. Use cargo bench --no-run to verify the benchmark builds without running measurements.

License

dream-ini is licensed under GPL-3.0-only.