redguard-preservation
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 ;
let game = parse_savegame_file?;
let slot = load_save_slot?;
println!;
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 ;
let system = parse?;
let combat = parse?;
let items = parse?;
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.
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 sourcesrc/import/- format importers/parserssrc/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 typesdocs/- format specifications and engine notestests/- integration tests
Quick Start
Install the CLI from crates.io:
To build from source, install the stable Rust toolchain, then run:
Show CLI help:
CLI Commands
read(r) โ parse an input file and print decoded structureconvert(c) โ export supported inputs to output formatsconvert <FILE>โ auto-detect format, use defaultsconvert <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.
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:
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):
Convert model with palette and asset root:
Convert RGM scene with palette and asset root:
Convert WLD world to GLB terrain + companion RGM placement:
Convert WLD terrain only:
Convert TEXBSI texture bank with palette:
Export all animation frames as separate PNGs:
Convert font to TrueType:
Export COL palette as JSON only:
Extract RTX dialogue with resolved filenames:
Scan a directory:
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.
