Expand description
§bdat-rs
BDAT is a proprietary binary file format used by MONOLITHSOFT for their Xenoblade Chronicles
games. It is a tabular data format (like CSV, TSV, etc.) with named tables and typed fields.
In newer versions of the format, files and tables (also called sheets) can be memory-mapped
for use in programs based on the C memory model.
This crate allows reading and writing BDAT tables and files.
§Reading BDAT tables
§Reading Xenoblade 3 (“modern”) tables
The crate exposes the from_bytes
and from_reader
functions in the modern
module
to parse Xenoblade 3 BDAT files from a slice or a std::io::Read
stream respectively.
The label_hash!
macro can be used to quickly generate hashed labels from plain-text strings.
See also: ModernTable
use bdat::{BdatResult, SwitchEndian, BdatFile, modern::ModernTable, label_hash};
use bdat::hash::murmur3_str;
fn read_xc3() -> BdatResult<()> {
let data = [0u8; 0];
// also bdat::from_reader for io::Read implementations.
let mut bdat_file = bdat::modern::from_bytes::<SwitchEndian>(&data)?;
let table: &ModernTable = &bdat_file.get_tables()?[0];
if table.name() == &label_hash!("CHR_PC") {
// Found the character table, get Noah's HP at level 99
let noah = table.row(1);
// Alternatively, if the `hash-table` feature is enabled (default)
let noah = table.row_by_hash(murmur3_str("PC_NOAH"));
let noah_hp = noah
.get(label_hash!("HpMaxLv99"))
.get_as::<u32>();
}
Ok(())
}
§Reading tables from other games (“legacy”)
Similarly, the legacy
module contains functions to read and write legacy tables.
There are differences between games that use the legacy format, so specifying a
BdatVersion
is required.
If you don’t know the legacy sub-version, you can use detect_file_version
or
detect_bytes_version
.
See also: LegacyTable
use bdat::{BdatResult, SwitchEndian, BdatFile, legacy::LegacyTable, LegacyVersion, Label};
fn read_legacy() -> BdatResult<()> {
// Mutable access is required as text might need to be unscrambled.
let mut data = [0u8; 0];
// Use `WiiEndian` for Xenoblade (Wii) and Xenoblade X.
let mut bdat_file = bdat::legacy::from_bytes::<SwitchEndian>(
&mut data,
LegacyVersion::Switch
)?;
let table: &LegacyTable = &bdat_file.get_tables()?[0];
if table.name() == "CHR_Dr" {
// Found the character table, get Rex's HP at level 99
let rex = table.row(1);
// We need to distinguish between legacy cell types
let rex_hp = rex.get("HpMaxLv99")
.as_single()
.unwrap()
.get_as::<u32>();
}
Ok(())
}
§Version auto-detect
If the table format isn’t known, from_bytes
and from_reader
from the
crate root can be used instead.
use bdat::{BdatResult, SwitchEndian, BdatFile, compat::CompatTable, Label, label_hash};
fn read_detect() -> BdatResult<()> {
// Mutable access is required, as this might be a legacy table.
let mut data = [0u8; 0];
// Endianness is also detected automatically.
let mut bdat_file = bdat::from_bytes(&mut data)?;
// Can no longer assume the format.
let table: &CompatTable = &bdat_file.get_tables()?[0];
if table.name() == label_hash!("CHR_PC") {
// Found the character table, get Noah's HP at level 99.
// No hash lookup for rows!
let noah = table.row(1);
// We can't use the ergonomic functions from `ModernTable` here,
// so we need to handle the legacy cases, even if they don't
// concern modern tables.
let noah_hp = noah.get(label_hash!("HpMaxLv99"))
.as_single()
.unwrap()
.get_as::<u32>();
}
Ok(())
}
§Writing BDAT tables
The to_vec
and to_writer
functions (in legacy
and modern
) can be used to write BDAT
files to a vector or a std::io::Write
implementation.
Writing fully requires the user to specify the BDAT version to use, by choosing the appropriate module implementation.
Tables obtained with the auto-detecting functions must be extracted or converted first.
use bdat::{BdatResult, LegacyVersion, SwitchEndian, WiiEndian};
use bdat::modern::ModernTable;
use bdat::legacy::LegacyTable;
fn write_modern(table: &ModernTable) -> BdatResult<()> {
// also bdat::to_writer for io::Write implementations
let _written: Vec<u8> = bdat::modern::to_vec::<SwitchEndian>([table])?;
Ok(())
}
fn write_legacy(table: &LegacyTable) -> BdatResult<()> {
// Endianness and version may vary, here it's writing Xenoblade X tables.
let _written: Vec<u8> = bdat::legacy::to_vec::<WiiEndian>([table], LegacyVersion::X)?;
Ok(())
}
§Serde support
When the serde
feature flag is enabled, this crate’s types will implement Serialize
and
Deserialize
.
While the crate doesn’t support serializing/deserializing BDAT to Rust types, this can be used
to transcode BDAT to other formats.
The bdat-toolset crate will convert BDAT to CSV and JSON, and JSON to BDAT.
Re-exports§
pub use label::Label;
Modules§
- Adapters for version-agnostic BDAT tables.
- Hash utilities (+ a murmur3 implementation) for XC3 BDATs
- Optionally hashed labels used as table and column names
- Table format and I/O operations for pre-XC3 (“legacy”) BDATs
- Table format and I/O operations for XC3 (“modern”) BDATs
Macros§
- Creates a murmur3-hashed
Label
from an expression.
Structs§
- A reference to a row that also keeps information about the parent table.
Enums§
- Errors that may occur while reading and writing BDAT tables
- The major categorization of the different BDAT formats.
- A cell from a BDAT row.
- Errors that may occur while detecting the version of a BDAT file.
- Subversion for legacy table formats.
- A value in a Bdat cell
- Discriminants for
Value
, generated automatically. - Compatibility file reader returned by
bdat::from_reader
- Compatibility slice reader returned by
bdat::from_bytes
Traits§
- Table extractor from a BDAT file.
Functions§
- Attempts to detect the BDAT version used in the given slice. The slice must include the full file header.
- Attempts to detect the BDAT version used in a file.
- Reads a BDAT file from a slice. The slice needs to have the full file data, though any unrelated bytes at the end will be ignored.
- Reads a BDAT file from a
std::io::Read
implementation. That type must also implementstd::io::Seek
.
Type Aliases§
- Alias for
Result<T, BdatError>
- Best-fit type for row IDs. In legacy BDATs, row identifiers are 16-bit. In modern BDATs, row IDs are 32-bit.
- Alias for
byteorder::LittleEndian
, i.e. the byte order used in Xenoblade 3D and in the Switch games. - An optionally-borrowed clone-on-write UTF-8 string.
- Alias for
byteorder::BigEndian
, i.e. the byte order used in the Wii/Wii U games.