Read, write, inspect, and manipulate Plasma engine PRP files used by Myst Online: Uru Live.
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 engine. Zero engine dependencies — no GPU, audio, or physics required.
- 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
cargo add plasma-prppip install plasma-prpcargo install plasma-prp --features cliuse 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));
}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);
}
}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.prpuse 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);
}
}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));
}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);
}
}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);
}
}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# 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/The parser handles all major Plasma object types, including:
| Category | Types |
|---|---|
| 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 |
- H-uru/Plasma — The open-source Plasma engine (C++)
- Korman — Blender plugin for creating Plasma ages
- libhsplasma — C++ library for Plasma file formats
This project is licensed under the GNU General Public License v3.0, matching the license used by Korman and the H-uru Plasma community tools.