1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
//! # ssbh_data
//!
//! ssbh_data provides a more intuitive and minimal API built on ssbh_lib.
//!
//! ## Features
//! The high level nature of ssbh_data makes it easier to integrate with application code than ssbh_lib.
//! Python bindings are also available with [ssbh_data_py](https://github.com/ScanMountGoat/ssbh_data_py).
//! - Automatic decoding and encoding of buffers and compressed data
//! - Usage of standard Rust types like [Vec] and [String]
//! - Support for converting files to and from supported versions
//! - Simpler output when serializing and deserializing
//! - Errors for invalid data such as out of bounds vertex indices
//! - Modifications are less likely to produce an invalid file due to reduced dependencies between fields
//!
//! ## Getting Started
//! The easiest way to access important items like [MeshData](crate::mesh_data::MeshData) is to import the [prelude].
//! For additional reading and writing options, see the [SsbhData] trait.
/*!
```no_run
use ssbh_data::prelude::*;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
// Read the file from disk.
let mut data = MeshData::from_file("model.numshb")?;
// Make some edits.
data.objects[0].name = "firstMesh".to_string();
// Save the changes.
data.write_to_file("model_new.numshb")?;
# Ok(())
# }
```
*/
//!
//! ## File Differences
//! The reduction in dependencies between fields and decoding and encoding of buffer data means
//! that ssbh_data does not guarantee an unmodified file to be binary identical after saving.
//! Examples include floating point rounding errors, larger file sizes due to different compression settings,
//! or default values used for unresearched flag values.
//! See the module level documentation for each format for details.
//!
//! These differences are minor in practice but may cause issues for some applications.
//! Applications needing a stronger guarantee that all data will be preserved
//! should use [ssbh_lib](https://crates.io/crates/ssbh_lib).
pub mod adj_data;
pub mod anim_data;
pub mod hlpb_data;
pub mod matl_data;
pub mod mesh_data;
pub mod meshex_data;
pub mod modl_data;
pub mod shdr_data;
pub mod skel_data;
use binrw::io::{Read, Seek, Write};
use ssbh_lib::prelude::*;
use std::convert::TryFrom;
use std::error::Error;
use std::path::Path;
pub use ssbh_lib::{CString, Color4f, Vector3, Vector4};
/// Functions for reading and writing supported formats.
pub trait SsbhData: Sized {
type WriteError: Error;
// TODO: Also specify the read error type?
/// Tries to read and convert the data from `reader`.
/// The entire file is buffered for performance.
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>>;
/// Tries to read and convert the data from `reader`.
/// For best performance when opening from a file, use [SsbhData::from_file] instead.
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self, Box<dyn std::error::Error>>;
/// Converts the data and writes to the given `writer`.
/// For best performance when writing to a file, use [SsbhData::write_to_file] instead.
fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<(), Self::WriteError>;
/// Converts the data and writes to the given `path`.
/// The entire file is buffered for performance.
fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::WriteError>;
}
/// Common imports for supported types and important traits.
pub mod prelude {
pub use crate::adj_data::AdjData;
pub use crate::anim_data::AnimData;
pub use crate::hlpb_data::HlpbData;
pub use crate::matl_data::MatlData;
pub use crate::mesh_data::MeshData;
pub use crate::meshex_data::MeshExData;
pub use crate::modl_data::ModlData;
pub use crate::shdr_data::ShdrData;
pub use crate::skel_data::SkelData;
pub use crate::SsbhData;
}
macro_rules! ssbh_data_impl {
($ssbh_data:ty, $ssbh_lib:ty, $error:ty) => {
impl SsbhData for $ssbh_data {
type WriteError = $error;
fn from_file<P: AsRef<std::path::Path>>(
path: P,
) -> Result<Self, Box<dyn std::error::Error>> {
<$ssbh_lib>::from_file(path)?.try_into().map_err(Into::into)
}
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self, Box<dyn std::error::Error>> {
<$ssbh_lib>::read(reader)?.try_into().map_err(Into::into)
}
fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<(), Self::WriteError> {
<$ssbh_lib>::try_from(self)?
.write(writer)
.map_err(Into::into)
}
fn write_to_file<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> Result<(), Self::WriteError> {
<$ssbh_lib>::try_from(self)?
.write_to_file(path)
.map_err(Into::into)
}
}
impl $ssbh_data {
/// Tries to read from `path`.
/// The entire file is buffered for performance.
pub fn from_file<P: AsRef<std::path::Path>>(
path: P,
) -> Result<Self, Box<dyn std::error::Error>> {
<Self as SsbhData>::from_file(path)
}
pub fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
) -> Result<Self, Box<dyn std::error::Error>> {
<Self as SsbhData>::read(reader)
}
pub fn write<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
) -> Result<(), <Self as SsbhData>::WriteError> {
<Self as SsbhData>::write(self, writer)
}
/// Write to `path.`
/// The entire write is buffered for performance.
pub fn write_to_file<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> Result<(), <Self as SsbhData>::WriteError> {
<Self as SsbhData>::write_to_file(self, path)
}
}
};
}
macro_rules! ssbh_data_infallible_impl {
($ssbh_data:ty, $ssbh_lib:ty, $error:ty) => {
impl SsbhData for $ssbh_data {
type WriteError = $error;
fn from_file<P: AsRef<std::path::Path>>(
path: P,
) -> Result<Self, Box<dyn std::error::Error>> {
Ok(<$ssbh_lib>::from_file(path)?.into())
}
fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
) -> Result<Self, Box<dyn std::error::Error>> {
Ok(<$ssbh_lib>::read(reader)?.into())
}
fn write<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
) -> Result<(), Self::WriteError> {
<$ssbh_lib>::from(self).write(writer)
}
fn write_to_file<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> Result<(), Self::WriteError> {
<$ssbh_lib>::from(self).write_to_file(path)
}
}
impl $ssbh_data {
/// Tries to read from `path`.
/// The entire file is buffered for performance.
pub fn from_file<P: AsRef<std::path::Path>>(
path: P,
) -> Result<Self, Box<dyn std::error::Error>> {
<Self as SsbhData>::from_file(path)
}
pub fn read<R: std::io::Read + std::io::Seek>(
reader: &mut R,
) -> Result<Self, Box<dyn std::error::Error>> {
<Self as SsbhData>::read(reader)
}
pub fn write<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
) -> Result<(), <Self as SsbhData>::WriteError> {
<Self as SsbhData>::write(self, writer)
}
/// Write to `path.`
/// The entire write is buffered for performance.
pub fn write_to_file<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> Result<(), <Self as SsbhData>::WriteError> {
<Self as SsbhData>::write_to_file(self, path)
}
}
};
}
ssbh_data_impl!(adj_data::AdjData, Adj, adj_data::error::Error);
ssbh_data_impl!(anim_data::AnimData, Anim, anim_data::error::Error);
ssbh_data_impl!(matl_data::MatlData, Matl, matl_data::error::Error);
ssbh_data_impl!(mesh_data::MeshData, Mesh, mesh_data::error::Error);
ssbh_data_infallible_impl!(meshex_data::MeshExData, MeshEx, std::io::Error);
ssbh_data_infallible_impl!(modl_data::ModlData, Modl, std::io::Error);
ssbh_data_infallible_impl!(hlpb_data::HlpbData, Hlpb, std::io::Error);
ssbh_data_impl!(skel_data::SkelData, Skel, skel_data::error::Error);
// TODO: ShdrData.
#[cfg(test)]
pub(crate) fn group_hex(a: &str, words_per_line: usize) -> String {
use itertools::Itertools;
// TODO: Find a cleaner way of doing this.
// ex: "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF..."
let words = a
.chars()
.collect::<Vec<char>>()
.chunks(8)
.map(|c| c.iter().collect::<String>())
.collect::<Vec<String>>();
words.chunks(words_per_line).map(|c| c.join(" ")).join("\n")
}
#[cfg(test)]
macro_rules! assert_hex_eq {
($a:expr, $b:expr) => {
pretty_assertions::assert_str_eq!(
crate::group_hex(&hex::encode($a), 8),
crate::group_hex(&hex::encode($b), 8)
)
};
}
#[cfg(test)]
pub(crate) use assert_hex_eq;