#assets #file #texture #sound #image #text #regnum #resource-index

regnumassets

A crate for parsing game asset files from MMORPG Champions of Regnum

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

Download history 117/week @ 2025-03-31 21/week @ 2025-04-07

138 downloads per month

MIT license

51KB
882 lines

A crate for parsing game asset files from MMORPG Champions of Regnum.

regnumassets on Crates.io Documentation License: MIT

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