redguard-preservation 0.10.2

A CLI tool to preserve Redguard files
Documentation

redguard-preservation

Crates.io

Preserving The Elder Scrolls Adventures: Redguard (1998) โ€” reverse-engineered file format specifications, engine documentation, and a Rust CLI for parsing and converting game assets.

๐Ÿ“– Full documentation โ€” file format specifications, engine internals, INI references, and more โ€” is published at https://michidk.github.io/redguard-preservation/. This README is the CLI / library / FFI quick reference.

Conversion Matrix

Input Type Input Extension(s) Output File(s)
Model .3d, .3dc .glb
ROB archive .rob .glb
RGM scene .rgm directory: .glb + scene/actors/navigation .json + per-actor .soup/.json scripts
WLD world .wld .glb + actor metadata .json (scene mode), or map .png set
Font .fnt bitmap .png + BMFont .fnt + glyph metadata .json, or .ttf
Visibility octree .pvo .json
Palette .col swatch .png + palette metadata .json
Sound effects bank .sfx extracted .wav files + index.json metadata (directory output)
Dialogue audio .rtx extracted .wav files + index.json metadata (directory output)
Software texture bank TEXTURE.### animated .gif or frame .png files + index.json
Software lookup table SHADE.###, HAZE.### grayscale index .png
Texture bank TEXBSI.### animated .gif + static .png files + index.json (directory output)
GXA bitmap archive .gxa animated .gif + index.json (directory output)
Cheat states .cht .json
PCX image .pcx .png
IFF packed bitmap .lbm .png
Deluxe Paint animation .anm directory: animation.gif + index.json

The scan command recursively detects known Redguard files in a directory tree. The CLI and Rust parsers focus on game assets. Hardware drivers, audio utilities, controller profiles, and development reports remain documented in Bundled support resources.

Rust inspection exports

import::wld::export_wld_metadata, import::model3d::export_model_metadata, and import::bsi::export_bsi_metadata expose decoded file data as JSON. Model metadata includes geometry, accumulated UVs in sixteenths of a texel, animation poses, and separately parsed subobjects. WLD metadata retains every map plane and includes decoded combined height, texture-index, and rotation grids. BSI metadata includes palettes and frame byte lengths; pixel buffers remain available through the image API.

gltf::model_position, gltf::model_normal, and gltf::wld_grid_position use the same coordinates as GLB exports. PvoFile::visibility_ids resolves a PLST leaf reference to placed-object IDs: IDs below the world's MPSO count select that record, while higher IDs require the runtime secondary object table.

Convert Subcommands

Each format has its own subcommand with scoped flags. convert <FILE> auto-detects the format and uses defaults.

Subcommand Input Output Format-specific flags
convert texbsi TEXBSI.### .gif / .png --format gif|png|frames, --palette, --compress-textures
convert gxa .gxa .gif / .png --format gif|png, --compress-textures
convert fnt .fnt .png + .fnt + .json, or .ttf --format bitmap|ttf, --compress-textures
convert col .col .png + .json --format png|json (default: both), --compress-textures
convert wld .wld .glb + .json, or map .png set --assets, --palette, --terrain-only, --terrain-textures, --compress-textures
convert model .3d, .3dc, .rob .glb --assets, --palette, --compress-textures
convert rgm .rgm output directory --assets, --palette, --compress-textures
convert rtx .rtx .wav + index.json --resolve-names
convert sfx .sfx .wav + index.json (none)
convert cht .cht .json (none)
convert pvo .pvo .json (none)
convert pcx .pcx .png (none)
convert lbm .lbm .png (none)
convert anm .anm animation.gif + index.json (none)

rgpre convert system/SKY_61.PCX and rgpre convert system/gui.lbm export PNG images. rgpre convert system/gui.anm writes system/gui_anm/animation.gif and timing metadata. The Rust entry points are rgpre::import::pcx::parse_pcx_file, rgpre::import::lbm::parse_lbm_file, and rgpre::import::anm::parse_anm_file.

ยน Exported TTF fonts are bitmap-traced vector outlines without hinting. They can be installed on Windows, used in game engines (Unity, Godot, etc.), and previewed at fontdrop.info.

Save-game loading

The read-only Rust API parses version 1.31 save containers and loads a slot directory:

use rgpre::import::savegame::{load_save_slot, parse_savegame_file};

let game = parse_savegame_file(&std::fs::read("SAVEGAME.SAV")?)?;
let slot = load_save_slot("SAVEGAME/SAVEGAME.002")?;
println!("{}: world {}", game.metadata.description, game.metadata.world_id);

SaveGame exposes decoded metadata and every chunk's original bytes in file order. SaveSlot contains the main save, a map of uppercase .TSG filenames to snapshots, and optional LOGBOOK.TXT bytes. Resource filename matching ignores case. Missing main saves, malformed containers, duplicate resource names, and non-regular resource files return errors with file context.

The loader only reads files. It does not follow the stored bitmap path, write saves, convert files, or restore actors and scripts into a running engine. State payloads remain opaque. Display strings use UTF-8 replacement characters for invalid bytes; the original bytes remain in SVGM. See Save-game containers.

Typed configuration parsers

The Rust library parses the gameplay configuration files into named structs:

File Entry point Contents
SYSTEM.INI rgpre::import::system_ini::SystemIni::parse Display, engine, debug, gameplay, player, object-manager, renderer, camera, dialogue, and Glide settings.
COMBAT.INI rgpre::import::combat_ini::CombatIni::parse Global combat settings and attacks, defenses, and taunts indexed by section ID.
SURFACE.INI rgpre::import::surface_ini::SurfaceIni::parse Texture surface classes and animation sound remaps; checked lookup and identity defaults.
ITEM.INI rgpre::import::item_ini::ItemIni::parse Inventory settings and item definitions indexed by item ID.
use rgpre::import::{combat_ini::CombatIni, item_ini::ItemIni, system_ini::SystemIni};

let system = SystemIni::parse(&std::fs::read_to_string("SYSTEM.INI")?)?;
let combat = CombatIni::parse(&std::fs::read_to_string("COMBAT.INI")?)?;
let items = ItemIni::parse(&std::fs::read_to_string("ITEM.INI")?)?;

These APIs require [system], [misc], and [items], respectively. Other sections and individual fields can be absent. Missing or empty fields are None; explicit zero remains Some(0). Signed engine units, floating-point camera factors, paths, RTX labels, and sparse IDs retain their values. Documented type codes have named enum variants, and undocumented codes use Unknown(i32).

Section and key matching ignores case. The first duplicate key wins. Semicolon comments, LF or CRLF line endings, and an initial UTF-8 BOM are accepted. Unknown fields are ignored. Invalid INI records, malformed recognized values, overflowing integers, non-finite floats, and invalid indexed identifiers return Error::Parse. Parsing retains stored values without applying engine defaults or runtime clamps.

The narrower rgpre::import::player_ini::PlayerConfig API remains available for ordinary grounded movement. It requires its six player settings and rejects duplicate relevant keys.

Documentation

The full documentation is published at https://michidk.github.io/redguard-preservation/ (rendered with mdBook). The Markdown sources live under docs/ and are organised into three sections:

  • File Formats (source) โ€” binary format specifications for models, textures, audio, maps, palettes, and other game assets
  • Engine Details (source) โ€” reverse-engineered engine internals: cheat system, item attachment, SOUP scripting, sky renderer, water waves
  • Configuration (source) โ€” text-based INI files shipped with the game (surface types, combat, keys, etc.)

Related projects

Cyrus is a modern, faithful Rust reimplementation of Redguard built on this project's research and the redguard-preservation crate. It ships an asset viewer that opens every file format documented here: models, worlds, textures, fonts, animations, scripts, and audio. Use it in the browser or download a native build from the latest Cyrus release.

Cyrus asset viewer showing a textured character model

Acknowledgements

RGUnity inspired redguard-preservation and provided foundational Redguard research used by this project. RGUnity is a human-developed Unity reimplementation built without AI assistance. Its contributors focus on meticulous fidelity and the small details that make the original game behave correctly.

redguard-preservation uses AI-assisted development to document broad working coverage quickly. This complements RGUnity's detail-first work. We are grateful to its contributors for their research, implementation, and continued care for Redguard.

Repository Layout

  • src/ - Rust source
  • src/import/ - format importers/parsers
  • src/gltf/ - GLTF/GLB conversion (builder, primitives, terrain, texture cache)
  • src/ffi/ - C-compatible FFI layer for native plugin use (Unity, etc.)
  • src/cli/ - CLI command handlers (cli/convert/ for per-format converters)
  • src/error.rs - shared error types
  • docs/ - format specifications and engine notes
  • tests/ - integration tests

Quick Start

Install the CLI from crates.io:

cargo install redguard-preservation

To build from source, install the stable Rust toolchain, then run:

cargo build

Show CLI help:

rgpre --help

CLI Commands

  • read (r) โ€” parse an input file and print decoded structure
  • convert (c) โ€” export supported inputs to output formats
    • convert <FILE> โ€” auto-detect format, use defaults
    • convert <FORMAT> <FILE> โ€” format-specific subcommand with scoped flags
  • scan (s) โ€” recursively scan a directory for known Redguard files

Asset Directories

Redguard shipped with two parallel sets of 3D assets for different renderers:

Directory Renderer Model versions Palette files Included in GOG
3dart/ Software v2.6, v2.7 18 COL palettes, including art_pal.col Original CD only
fxart/ Glide (3dfx) v4.0, v5.0 27 populated COL palettes; one empty placeholder โœ“

The library decodes software and Glide model geometry and animation frames. Textured exports use software TEXTURE.* or Glide TEXBSI.* banks. Software SHADE.* and HAZE.* lookup tables have a separate parser. See CD and GOG differences for resource selection.

Model exports select their texture format from the model version. For scene or terrain exports from an install containing both art trees, use convert rgm ... --software or convert wld ... --software. Rust consumers can select the edition with TextureCache::new_for_edition, registry::scan_dir_for_edition, and WorldHandle::open_for_edition. The equivalent C entry point is rg_open_world_for_edition.

rgpre convert texture 3dart/TEXTURE.302 --palette 3dart/ISLAND.COL --format frames -o output/texture_302
rgpre convert shade 3dart/SHADE.000 -o output/shade_000
rgpre convert haze 3dart/HAZE.000 -o output/haze_000
rgpre convert rgm maps/START.RGM --assets . --software -o output/start.glb

Texture exports include index.json with record metadata. Without --palette, image exports display indices as grayscale. Lookup exports write indices.png, preserving each mapping byte as a grayscale value.

To check every model frame, texture frame and lookup table in a local art directory:

cargo run --example check_edition_assets -- /path/to/Redguard/3dart
cargo run --example check_edition_assets -- /path/to/Redguard/fxart

The fxart/ Glide assets are included in GOG and use the newer model format. Each scene uses its own palette, such as ISLAND.COL or NECRO.COL. Pass the matching palette via --palette for textured exports.

Usage Examples

Auto-detect format (uses defaults):

rgpre convert 3dart/LHBM4.3DC -o output/LHBM4.glb
rgpre convert fxart/TEXBSI.302 -o output/TEXBSI_302/

Convert model with palette and asset root:

rgpre convert model fxart/BELLTOWR.ROB --palette fxart/ISLAND.COL --assets . -o output/BELLTOWR.glb

Convert RGM scene with palette and asset root:

rgpre convert rgm maps/BELLTOWR.RGM --palette fxart/ISLAND.COL --assets . -o output/BELLTOWR_scene.glb

Convert WLD world to GLB terrain + companion RGM placement:

rgpre convert wld maps/ISLAND.WLD --palette fxart/ISLAND.COL --assets . -o output/ISLAND_world.glb

Convert WLD terrain only:

rgpre convert wld maps/ISLAND.WLD --palette fxart/ISLAND.COL --assets . --terrain-only -o output/ISLAND_terrain.glb

Convert TEXBSI texture bank with palette:

rgpre convert texbsi fxart/TEXBSI.302 --palette fxart/ISLAND.COL -o output/TEXBSI_302/

Export all animation frames as separate PNGs:

rgpre convert texbsi fxart/TEXBSI.302 --palette fxart/ISLAND.COL --format frames -o output/TEXBSI_302/

Convert font to TrueType:

rgpre convert fnt input/FONT01.FNT --format ttf -o output/FONT01.ttf

Export COL palette as JSON only:

rgpre convert col fxart/ISLAND.COL --format json -o output/ISLAND.json

Extract RTX dialogue with resolved filenames:

rgpre convert rtx input/DIALOG.RTX --resolve-names -o output/dialog/

Scan a directory:

rgpre scan 3dart

Native Plugin (FFI)

The library builds as a C-compatible shared library (cdylib) alongside the CLI binary. This lets game engines like Unity load it as a native plugin and call the conversion functions directly at runtime.

See src/ffi/README.md for the full API reference โ€” architecture, C struct definitions, function signatures, memory management, and error handling.