# plasma-prp
Read, write, inspect, and manipulate Plasma engine PRP files used by [Myst Online: Uru Live](https://mystonline.com/).
A standalone Rust library (with Python bindings and CLI) for working with the binary `.prp`, `.age`, `.sdl`, and `.fni` file formats from the [H-uru/Plasma](https://github.com/H-uru/Plasma) engine. Zero engine dependencies — no GPU, audio, or physics required.
## Features
- **Parse** all object types in PRP files (scenes, materials, textures, physics, audio, animations, responders, Python script mods, and more)
- **Write** PRP files back with byte-identical round-trip fidelity
- **Inspect** PRP/AGE/SDL files from the command line
- **Diff** two PRP files to see what changed
- **Export** textures as PNG (DXT1/DXT5 decompression included)
- **Validate** PRP files for common issues (missing refs, broken pointers)
- **Python bindings** via PyO3 — `pip install plasma-prp`
## Install
### Rust
```sh
cargo add plasma-prp
```
### Python
```sh
pip install plasma-prp
```
### CLI
```sh
cargo install plasma-prp --features cli
```
## Rust Usage
### Load a PRP and list all objects
```rust
use plasma_prp::{PrpPage, ClassIndex};
let page = PrpPage::from_file("Cleft_District_Cleft.prp".as_ref())?;
println!("{} objects in {}", page.keys.len(), page.header.age_name);
for key in &page.keys {
println!(" [0x{:04X}] {} ({})",
key.class_type,
key.object_name,
ClassIndex::class_name(key.class_type));
}
```
### Extract textures as PNG
```rust
use plasma_prp::{PrpPage, ObjectKey};
use plasma_prp::resource::prp::{class_types, MipmapData};
let page = PrpPage::from_file("Cleft_District_Textures.prp".as_ref())?;
for key in page.keys_of_type(class_types::PL_MIPMAP) {
if let Some(data) = page.object_data(key) {
let mip = MipmapData::parse(data)?;
println!("Texture: {} ({}x{}, {} levels)",
mip.name, mip.width, mip.height, mip.num_levels);
}
}
```
### Round-trip a PRP file
```rust
use plasma_prp::PrpPage;
let page = PrpPage::from_file("input.prp".as_ref())?;
page.save("output.prp".as_ref())?;
// output.prp is byte-identical to input.prp
```
### Diff two PRP files
```rust
use plasma_prp::PrpPage;
let a = PrpPage::from_file("before.prp".as_ref())?;
let b = PrpPage::from_file("after.prp".as_ref())?;
for key in &b.keys {
if !a.keys.iter().any(|k| k.object_name == key.object_name
&& k.class_type == key.class_type)
{
println!("+ Added: {}", key.object_name);
}
}
```
### Read an Age file and its pages
```rust
use plasma_prp::AgeDescription;
let age = AgeDescription::from_file("Cleft.age".as_ref())?;
println!("Age: {} (prefix {})", age.age_name, age.sequence_prefix);
for page in age.auto_load_pages() {
println!(" Auto-load: {} -> {}", page.name, age.prp_filename(page));
}
```
### Access materials and layers
```rust
use plasma_prp::resource::prp::{parse_layer_state, class_types};
use plasma_prp::PrpPage;
let page = PrpPage::from_file("Cleft_District_Cleft.prp".as_ref())?;
for key in page.keys_of_type(class_types::PL_LAYER) {
if let Some(data) = page.object_data(key) {
let layer = parse_layer_state(data)?;
println!("Layer '{}': texture={:?}, opacity={:.2}",
layer.name, layer.texture_name, layer.opacity);
}
}
```
### Parse SDL descriptors
```rust
use plasma_prp::sdl::SdlManager;
let mut mgr = SdlManager::new();
mgr.load_directory("SDL/".as_ref())?;
if let Some(desc) = mgr.find("Cleft", 0) {
println!("Cleft SDL v{}: {} variables", desc.version, desc.variables.len());
for var in &desc.variables {
println!(" {:?} {}[{}]", var.var_type, var.name, var.count);
}
}
```
## Python Usage
```python
import plasma_prp
# Load a PRP file
prp = plasma_prp.PrpFile.load("Cleft_District_Cleft.prp")
print(f"{len(prp)} objects in {prp.age_name}")
# List objects by type
for class_name, count in sorted(prp.count_by_type().items()):
print(f" {count:4d} {class_name}")
# Access specific object types
for obj in prp.objects_by_name("plSceneObject"):
scene = obj.as_scene_object()
print(f"SceneObject: {scene.name}")
# Parse materials
for obj in prp.objects_by_name("plLayer"):
layer = obj.as_layer()
print(f"Layer '{layer.name}': texture={layer.texture_name}")
# Parse an age file
age = plasma_prp.AgeFile.load("Cleft.age")
for page in age.pages:
print(f" {page.name} (auto_load={page.auto_load})")
# Load SDL descriptors
sdl = plasma_prp.SdlFile()
sdl.load_directory("SDL/")
desc = sdl.find("Cleft", 0)
for var in desc.variables:
print(f" {var.var_type} {var.name}[{var.count}]")
# Round-trip
prp.save("output.prp") # byte-identical to input
```
## CLI Usage
```sh
# Inspect a PRP file — object inventory grouped by class
plasma-prp inspect Cleft_District_Cleft.prp
# Inspect an age file — page list with auto-load flags
plasma-prp inspect Cleft.age
# Inspect SDL descriptors
plasma-prp inspect avatar.sdl
# Validate a PRP file — check for missing refs, broken pointers
plasma-prp validate Cleft_District_Cleft.prp
# Diff two PRP files — added/removed/modified objects
plasma-prp diff before.prp after.prp
# Export all textures as PNG
plasma-prp export-textures Cleft_District_Textures.prp ./textures/
```
## Supported Object Types
The parser handles all major Plasma object types, including:
| Scene | plSceneObject, plSceneNode, plCoordinateInterface, plDrawInterface |
| Materials | hsGMaterial, plLayer, plLayerAnimation, plLayerSDLAnimation |
| Textures | plMipmap, plCubicEnvironmap, plDynamicTextMap |
| Animation | plATCAnim, plAGMasterMod, plAGModifier, keyframe controllers |
| Physics | plPXPhysical (trimesh, convex hull, box, sphere) |
| Audio | plWin32StaticSound, plWin32StreamingSound, plSoundBuffer |
| Logic | plResponderModifier, plLogicModifier, plOneShotMod |
| Scripting | plPythonFileMod (parameters, receivers) |
| Volumes | plObjectInVolumeDetector, plSoftVolume (simple, union, intersect, invert) |
| Visibility | plVisRegion, plRelevanceRegion |
| Camera | plCameraBrain1, plCameraBrain1_Avatar, plCameraBrain1_Fixed |
| Lighting | plDirectionalLightInfo, plOmniLightInfo, plSpotLightInfo |
| GUI | pfGUIDialogMod, pfGUIButtonMod, pfGUITextBoxMod |
| Effects | plParticleSystem, plWaveSet7, plFogEnvironment |
| Decals | plDynaFootMgr, plDynaRippleMgr, plDynaWakeMgr |
| Reverb | plEAXListenerMod |
| Vegetation | plClusterGroup |
## Related Projects
- [H-uru/Plasma](https://github.com/H-uru/Plasma) — The open-source Plasma engine (C++)
- [Korman](https://github.com/H-uru/korman) — Blender plugin for creating Plasma ages
- [libhsplasma](https://github.com/H-uru/libhsplasma) — C++ library for Plasma file formats
## License
This project is licensed under the [GNU General Public License v3.0](LICENSE), matching the license used by Korman and the H-uru Plasma community tools.