3 releases (breaking)
new 0.3.0 | Apr 18, 2025 |
---|---|
0.2.0 | Apr 3, 2025 |
0.1.0 | Jan 20, 2024 |
#672 in Parser implementations
138 downloads per month
51KB
882 lines
A crate for parsing game asset files from MMORPG Champions of Regnum.
Table of Contents
About
This crate provides a set of tools for parsing/extracting asset files from your local installation of Champions of Regnum.
Basic Usage
Champions of Regnum comes with 2 types of asset files: index files and database files. Both of these files are located in the game installation folder and use the .idx
and .sdb
extensions respectively. Each file contains a given set of asset files, that could be either sounds, music, textures, etc. For each index file there's a corresponding database files. Index files do not include the assets but provide information on how to retrieve a given asset from the database file.
The process of retrieving data from asset files consist of parsing an index file using ResourceIndex
to generate a list of bookmarks. Each bookmark points to a particular asset in the database file. Bookmarks keep track of where in the database file an asset is located, allowing for parsing and extracting their contents.
The next example shows how to retrieve the list of sounds from the corresponding index file:
use anyhow::Result;
use regnumassets::{AssetType, ResourceIndex};
use std::fs::File;
fn main() -> Result<()> {
let f = File::open("data2.idx")?;
let index = ResourceIndex::read(f).unwrap();
let sounds = index.filter_by_type(AssetType::Sound);
for sound in &sounds {
println!(
"Resource #{}: {}",
sound.resource_id.unwrap_or(0),
sound.name.as_deref().unwrap_or("(unnamed)".into())
);
}
Ok(())
}
The AssetType
enum defines the following variants:
pub enum AssetType {
Material,
Animation,
Mesh,
Image,
Text,
Binary,
Texture,
Font,
Effect,
Music,
Sound,
Character,
Auth,
}
Asset data
The ResourceIndex
struct provides an API for retrieving assets either by their resource id or by their asset type. Calling these methods will get you an instance of AssetBookmark
. To get data from a database file we use the AssetData
struct, which has a constructor expecting the database file handle and a &AssetBookmark
.
fn main() -> Result<()> {
let f = File::open("data2.idx")?;
let index = ResourceIndex::read(f).unwrap();
let sound = index.get_by_resource_id(50677).unwrap();
let f = File::open("data2.sdb")?;
let asset = AssetData::read(&f, &sound).unwrap();
// ...
}
The AssetData
struct includes a content
property that contains the actual asset. This enum type defines a variant for each supported type. Types that are not currently supported will always generate a value of type AssetContent::NotSupported
.
pub enum AssetContent {
/// A variant holding an Ogg Vorbis file
Sound {
filename: String,
size: u32,
bytes: Vec<u8>,
},
/// A variant holding a Direct Draw Surface
Texture { width: u32, height: u32, dds: Dds },
/// A variant holding a list of text components
Text { contents: Vec<TextContent> },
/// A variant holding a JPEG image
Image { bytes: Vec<u8> },
/// A variant indicating a not-supported content
NotSupported,
}
Text
Within the game, a text can either be used for sinple translation or to build complex quest scripts. To cover all these different cases, texts are parsed into a list of TextContent
:
pub struct TextContent {
pub refs: Vec<String>,
pub nodes: Vec<TextNode>,
}
All text components include a list of numerical identifiers (refs
). These refs
are used to identify a node and contain at least one element. Each node also contains a list of string elements of type TextNode
. This enum is able to identify cases where a text refers to a topic or a particular quest stage:
pub enum TextNode {
/// The beginning of a list of text nodes
Start,
/// The end of a list of text nodes
End,
/// A number indicating a stage in a quest
Stage(u32),
/// A string indicating a topic/theme
Topic(String),
/// A free form text
Content(String),
}
This next example parses eng_npc_template_dialog
, a resource including NPC dialog from the game:
use anyhow::Result;
use regnumassets::{ResourceIndex, AssetData, AssetContent};
use std::fs::File;
fn main() -> Result<()> {
let f = File::open("examples/regnum/data5.idx")?;
let index = ResourceIndex::read(f).unwrap();
let text = index.get_by_resource_id(51277).unwrap();
let f = File::open("examples/regnum/data5.sdb")?;
let asset = AssetData::read(&f, &text).unwrap();
match asset.content {
AssetContent::Text { contents } => {
for content in contents {
println!("refs: {:?}", content.refs);
for node in &content.nodes {
println!("TEXT: {:?}", node);
}
}
}
_ => {
println!("could not read text asset")
}
}
Ok(())
}
Keep in mind that regular text can also include format elements to change font color and such. This library does not provide features for post-processing these type of strings.
Sound
Both music and sound are stored using the Ogg Vorbis format. The bytes
attribute will include the raw data. A filename
attribute is also included.
The next example shows how to obain a sound by its id and save the contents to a new file:
use anyhow::Result;
use regnumassets::{ResourceIndex, AssetData, AssetContent};
use std::fs::File;
use std::io::Write;
fn main() -> Result<()> {
let f = File::open("data2.idx")?;
let index = ResourceIndex::read(f).unwrap();
let sound = index.get_by_resource_id(50677).unwrap();
let f = File::open("data2.sdb")?;
let asset = AssetData::read(&f, &sound).unwrap();
match asset.content {
AssetContent::Sound {
bytes,
filename,
size,
} => {
println!("writing {} bytes file to {}", size, filename);
let mut output = File::create(filename)?;
output.write_all(bytes.as_ref())?;
output.flush()?;
}
_ => {
println!("couuld not parse sound asset")
}
}
Ok(())
}
Texture
Textures are stored using the DirectDraw Surface format. In order to parse these assets, the ddsfile::Dds struct is used. This struct can then be used to export the contents to a .dds
file.
use anyhow::Result;
use regnumassets::{ResourceIndex, AssetData, AssetContent};
use std::fs::File;
use std::io::Write;
fn main() -> Result<()> {
let f = File::open("examples/regnum/data6.idx")?;
let index = ResourceIndex::read(f).unwrap();
let texture = index.get_by_resource_id(85953).unwrap();
let f = File::open("examples/regnum/data6.sdb")?;
let asset = AssetData::read(&f, &texture).unwrap();
match asset.content {
AssetContent::Texture { width, height, dds } => {
println!(
"writing texture '{}' ({}x{}) to out.dds",
asset.asset_name, width, height
);
let mut file = File::create("out.dds")?;
dds.write(&mut file)?;
file.flush()?;
}
_ => {
println!("this content is not supported")
}
}
Ok(())
}
Image
Images are stored using the JPEG
(JFIF
) format. These assets are exported as slices of bytes.
use anyhow::Result;
use regnumassets::{AssetContent, AssetData, ResourceIndex};
use std::fs::File;
use std::io::Write;
fn main() -> Result<()> {
let f = File::open("data5.idx")?;
let index = ResourceIndex::read(f).unwrap();
let image = index.get_by_resource_id(75879).unwrap();
let f = File::open("data5.sdb")?;
let asset = AssetData::read(&f, &image).unwrap();
match asset.content {
AssetContent::Image { bytes } => {
let filename = "out.jpeg";
println!("writing file to {}", filename);
let mut output = File::create(filename)?;
output.write_all(bytes.as_ref())?;
output.flush()?;
}
_ => {
println!("couuld not parse image asset")
}
}
Ok(())
}
License
Released under the MIT License.
Disclaimer
Champions of Regnum is a registered trademark of Nimble Giant Entertainment. I don't hold any type of relation to the company or its staff.
Dependencies
~6.5MB
~186K SLoC