An encoder/decoder for Konami's binary XML format, used in many of their games.
Requires Rust 1.34 or newer!
- Setup Rust through
rustup
or your own preferred method of acquiring Rust - For using
kbinxml-rs
as a library, add it as a dependency in yourCargo.toml
file- For example, if you want the base library and psmap_derive:
cargo add kbinxml psmap psmap_derive --git https://github.com/mbilker/kbinxml-rs.git
- For using
kbinxml-rs
as a standalone application, installkbinxml-rs
usingcargo install kbinxml --features=build_binary
(Note: This will not work at the moment askbinxml-rs
has not yet been published to crates.io)
use kbinxml;
fn main() {
let input = b"
<?xml version='1.0'?>
<test>
<entry __type=\"str\" some_attr=\"an attribute\">Hello, world!</entry>
</test>
";
let (nodes, encoding) = kbinxml::from_text_xml(input).unwrap();
println!("encoding: {}, data: {:?}", encoding, nodes);
}
This prints (prettified for the README):
encoding: UTF-8, data: NodeCollection {
base: NodeDefinition {
encoding: UTF_8,
node_type: NodeStart,
is_array: false,
data: Some {
key: Uncompressed { "test" },
value_data: b""
}
},
attributes: [],
children: [
NodeCollection {
base: NodeDefinition {
encoding: UTF_8,
node_type: String,
is_array: false,
data: Some {
key: Uncompressed { "entry" },
value_data: b"Hello, world!\0"
}
},
attributes: [
NodeDefinition {
encoding: UTF_8,
node_type: Attribute,
is_array: false,
data: Some {
key: Uncompressed { "some_attr" },
value_data: b"an attribute\0"
}
}
],
children: []
}
]
}
use std::fs;
use kbinxml;
fn main() {
let input = fs::read("testcases_out.kbin").unwrap();
let (nodes, encoding) = kbinxml::from_binary(input.into()).unwrap();
println!("encoding: {}, data: {:?}", encoding, nodes);
}
use kbinxml;
use psmap;
use psmap_derive::*;
#[derive(Debug)]
struct Entry {
hello: String,
some_attr: String,
sub_entry_value: Option<u8>,
sub_entry_attr: u8,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = b"
<?xml version='1.0'?>
<test>
<entry __type=\"str\" some_attr=\"an attribute\">
Hello, world!
<sub_entry __type=\"u8\" another_attr=\"10\">64</sub_entry>
</entry>
</test>
";
let (nodes, _encoding) = kbinxml::from_text_xml(input).unwrap();
// the container NodeCollection must be turned into a single node for psmap
let node = nodes.as_node().unwrap();
// psmap uses `?` internally, so the calling function must return a Result.
// If you're making a larger project, using `anyhow::Result` is recommended.
let value = psmap! {
output: Entry,
inputs: [
// the top-level node "test" is inaccessible, only the child nodes
// and attributes are usable inside psmap
node: {
"entry" => {
attributes: {
"some_attr" => some_attr,
},
value => hello,
"sub_entry" => {
attributes: {
// attributes can be parsed with `as`
"another_attr" => sub_entry_attr as u8,
},
value => sub_entry_value,
// A value can be made optional using this keyword
optional,
},
}
},
],
};
println!("{:?}", value);
Ok(())
}
This code prints:
Entry {
hello: "Hello, world!",
some_attr: "an attribute",
sub_entry_value: Some(64),
sub_entry_attr: 10
}
use kbinxml;
use psmap;
use psmap_derive::*;
#[derive(Debug)]
struct Entry {
id: u8,
name: String,
color: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = b"
<?xml version='1.0'?>
<test>
<!-- Leaf nodes with no type are 'str' by default -->
<entry id=\"5\"><name>Banana</name><color>Yellow</color></entry>
<entry id=\"8\"><name>Apple</name><color>Red</color></entry>
<entry id=\"1\"><name>Peach</name><color>Pink</color></entry>
<entry id=\"2\"><name>Mulberry</name><color>Violet</color></entry>
<entry id=\"7\"><name>Rockmelon</name><color>Orange</color></entry>
<entry id=\"4\"><name>Disguised Banana</name><color>Invisible</color></entry>
</test>
";
let (nodes, _encoding) = kbinxml::from_text_xml(input).unwrap();
let entries: Result<Vec<Entry>, _> = nodes.children().iter()
.map(|node_collection| -> Result<Entry, Box<dyn std::error::Error>> {
let node = node_collection.as_node()?;
// because we cannot access the top level node "entry", we get its
// attribute first
let id = node
.attributes()
.get("id").expect("id not present")
.parse::<u8>()?;
// we then provide the `id` to `psmap` using "include"
Ok(psmap! {
output: Entry,
include: [id],
inputs: [
node: {
"name" => name,
"color" => color,
},
],
})
})
.collect();
println!("{:?}", entries);
Ok(())
}
This code prints (prettified for the README):
Ok([
Entry { id: 5, name: "Banana", color: "Yellow" },
Entry { id: 8, name: "Apple", color: "Red" },
Entry { id: 1, name: "Peach", color: "Pink" },
Entry { id: 2, name: "Mulberry", color: "Violet" },
Entry { id: 7, name: "Rockmelon", color: "Orange" },
Entry { id: 4, name: "Disguised Banana", color: "Invisible" }
])
let text = kbinxml::to_text_xml(&nodes).unwrap();
// if you encoded in UTF8 and want a str:
let text = std::str::from_utf8(&text).unwrap();
let bytes = kbinxml::to_binary(&nodes).unwrap();
let options = kbinxml::Options::builder()
.compression(kbinxml::CompressionType::Compressed)
.encoding(kbinxml::EncodingType::SHIFT_JIS)
.build();
let bytes = kbinxml::to_binary_with_options(options, &nodes).unwrap();
There is currently no way to seralise structs directly as with psmap
, they
must be converted into nodes manually.