cdragon_prop/
lib.rs

1//! Support of Riot PROP files
2//!
3//! # Overview
4//!
5//! PROP files, more commonly called *bin* files, contain nested data structures.
6//! All structures are typed and usually abide to the same type, but type is provided in the data
7//! itself, so there is no need to have a schema to decode it.
8//!
9//! Each PROP File contains a list of [entries](BinEntry) which itself contains a nested list of
10//! [fields](BinField) of various [data types](BinType).
11//!
12//! # Dynamic types
13//!
14//! Container types store data whose type is defined dynamically.
15//! Since Rust types are defined statically, they cannot be used directly.
16//! Moreover, even if the embedded bin type is known by the container at run-time, the Rust type
17//! system requires the user to explicitely request a given type, one way or the other.
18//!
19//! The [`binget!()`] macro makes it easier to chain casts and should be enough when the names and
20//! types to get are known in advance.
21//!
22//! Data can also be casted explicitely, using the `downcast()` method provided by
23//! all types wrapping dynamically typed data:
24//! ```no_run
25//! # use cdragon_prop::data::*;
26//! # fn test(field: BinField) {
27//! field.downcast::<BinString>();
28//! # }
29//! // => `Some(BinString)` if field contains a string, `None` otherwise
30//! ```
31//! Containers with fields provide a `getv()` helper to follow a `get()` with a `downcast()`.
32//!
33//! If all possible types have to be handled, the [`binvalue_map_type!()`] can be used to provide a
34//! single generic expression to handle all possible types.
35//!
36//! Map keys support only a subset or types. [`binvalue_map_keytype!()`] can be used to map only
37//! key types. It can be combined with another [`binvalue_map_type!()`] to handle both keys and
38//! values.
39//!
40//! **Note:** those macros are expanded to a `match` handling all possible cases; resulting code can be
41//! large.
42//!
43//! ## Examples
44//! ```
45//! # use cdragon_prop::{binvalue_map_keytype, binvalue_map_type, data::*};
46//! # fn test(field: BinField, map: BinMap) {
47//! binvalue_map_type!(field.vtype, T, {
48//!     let value: &T = field.downcast::<T>().unwrap();
49//! });
50//!
51//! binvalue_map_keytype!(map.ktype, K,
52//!     binvalue_map_type!(map.vtype, V, {
53//!         let entries: &Vec<(K, V)> = map.downcast::<K, V>().unwrap();
54//!     })
55//! );
56//! # }
57//! ```
58//!
59//! # Bin hashes
60//!
61//! Bin files use 32-bit FNV-1a hashes for several identifier names:
62//!
63//! - [entry paths](BinEntryPath)
64//! - [class names](BinClassName)
65//! - [field names](BinFieldName)
66//! - ["bin hash" values](BinHashValue).
67//!
68//! Hash values can be computed at compile-time with [`cdragon_hashes::binh!()`], or
69//! using other methods from [`cdragon_hashes::bin`].
70//!
71//! A [`BinHashMappers`] gather all hash-to-string conversion needed by bin data.
72
73mod macros;
74mod parser;
75mod serializer;
76mod text_tree;
77mod json;
78pub mod visitor;
79pub mod data;
80
81use std::io;
82use std::fs;
83use std::path::Path;
84use thiserror::Error;
85use cdragon_hashes::{HashKind, HashMapper, HashError};
86use cdragon_utils::parsing::ParseError;
87pub use cdragon_hashes::bin::{BinHashKind, BinHashMapper};
88
89pub use serializer::{BinSerializer, BinEntriesSerializer};
90pub use data::*;
91pub use parser::{BinEntryScanner, BinEntryScannerItem};
92pub use text_tree::TextTreeSerializer;
93pub use json::JsonSerializer;
94pub use visitor::{BinVisitor, BinTraversal};
95
96
97/// Result type for PROP file errors
98type Result<T, E = PropError> = std::result::Result<T, E>;
99
100
101/// Generic type to associate each kind of hash from bin files to a value
102#[allow(missing_docs)]
103#[derive(Debug)]
104pub struct BinHashKindMapping<T, U> {
105    pub entry_path: T,
106    pub class_name: T,
107    pub field_name: T,
108    pub hash_value: T,
109    pub path_value: U,
110}
111
112impl<T, U> BinHashKindMapping<T, U> {
113    /// Give access to a specific field from its kind
114    #[inline]
115    pub fn get(&self, kind: BinHashKind) -> &T {
116        match kind {
117            BinHashKind::EntryPath => &self.entry_path,
118            BinHashKind::ClassName => &self.class_name,
119            BinHashKind::FieldName => &self.field_name,
120            BinHashKind::HashValue => &self.hash_value,
121        }
122    }
123
124    /// Give mutable access to a specific mapper from its kind
125    #[inline]
126    pub fn get_mut(&mut self, kind: BinHashKind) -> &mut T {
127        match kind {
128            BinHashKind::EntryPath => &mut self.entry_path,
129            BinHashKind::ClassName => &mut self.class_name,
130            BinHashKind::FieldName => &mut self.field_name,
131            BinHashKind::HashValue => &mut self.hash_value,
132        }
133    }
134}
135
136impl<T: Default, U: Default> Default for BinHashKindMapping<T, U> {
137    fn default() -> Self {
138        Self {
139            entry_path: T::default(),
140            class_name: T::default(),
141            field_name: T::default(),
142            hash_value: T::default(),
143            path_value: U::default(),
144        }
145    }
146}
147
148
149/// Hash mappers for all kinds of bin hashes
150///
151/// Each individual mapper can be accessed either directly through its field, or from a
152/// `BinHashKind` value.
153pub type BinHashMappers = BinHashKindMapping<BinHashMapper, HashMapper<u64, 64>>;
154
155impl BinHashMappers {
156    /// Create mapper, load all sub-mappers from a directory path
157    pub fn from_dirpath(path: &Path) -> Result<Self, HashError> {
158        let mut this = Self::default();
159        this.load_dirpath(path)?;
160        Ok(this)
161    }
162
163    /// Load all sub-mappers from a directory path
164    pub fn load_dirpath(&mut self, path: &Path) -> Result<(), HashError> {
165        self.entry_path.load_path(path.join(HashKind::BinEntryPath.mapping_path()))?;
166        self.class_name.load_path(path.join(HashKind::BinClassName.mapping_path()))?;
167        self.field_name.load_path(path.join(HashKind::BinFieldName.mapping_path()))?;
168        self.hash_value.load_path(path.join(HashKind::BinHashValue.mapping_path()))?;
169        self.path_value.load_path(path.join(HashKind::WadGame.mapping_path()))?;
170        Ok(())
171    }
172
173    /// Write all sub-mappers to a directory path
174    pub fn write_dirpath(&self, path: &Path) -> Result<(), HashError> {
175        self.entry_path.write_path(path.join(HashKind::BinEntryPath.mapping_path()))?;
176        self.class_name.write_path(path.join(HashKind::BinClassName.mapping_path()))?;
177        self.field_name.write_path(path.join(HashKind::BinFieldName.mapping_path()))?;
178        self.hash_value.write_path(path.join(HashKind::BinHashValue.mapping_path()))?;
179        self.path_value.write_path(path.join(HashKind::WadGame.mapping_path()))?;
180        Ok(())
181    }
182}
183
184/// PROP file, with entries
185///
186/// This structure contains all the data of a PROP file, completely parsed.
187/// It also provides methods to simply scan an file, without storing all the data, and possibly
188/// skipping unneeded data.
189#[derive(Debug)]
190pub struct PropFile {
191    /// PROP version
192    pub version: u32,
193    /// `true` for patch file
194    ///
195    /// Patch files are used to hot-patch data from other, regular files.
196    /// They are usually much slower and a notably used by Riot to update the game without a new
197    /// release (patches are then provided directly by the server when the game starts).
198    pub is_patch: bool,
199    /// List of paths to other PROP files
200    pub linked_files: Vec<String>,
201    /// List of bin entries
202    pub entries: Vec<BinEntry>,
203}
204
205impl PropFile {
206    /// Parse a whole `PropFile` from data
207    pub fn from_slice(data: &[u8]) -> Result<PropFile> {
208        Ok(parser::binparse(data)?)
209    }
210
211    /// Parse a whole `PropFile` from data
212    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<PropFile> {
213        Self::from_slice(&fs::read(path.as_ref())?)
214    }
215
216    /// Iterate on entry headers (path and type) from a PROP reader
217    pub fn scan_entries_from_reader<R: io::Read>(reader: R) -> Result<BinEntryScanner<R>> {
218        let scanner = BinEntryScanner::new(reader)?;
219        Ok(scanner)
220    }
221
222    /// Iterate on entry headers (path and type) from a PROP file path
223    pub fn scan_entries_from_path<P: AsRef<Path>>(path: P) -> Result<BinEntryScanner<io::BufReader<fs::File>>> {
224        let file = fs::File::open(path)?;
225        let reader = io::BufReader::new(file);
226        let scanner = BinEntryScanner::new(reader)?;
227        Ok(scanner)
228    }
229}
230
231/// Entry header, used by parsers that iterate on entries
232pub type BinEntryHeader = (BinEntryPath, BinClassName);
233
234/// Entry in a PROP file
235#[derive(Debug)]
236pub struct BinEntry {
237    /// Entry path (hashed)
238    pub path: BinEntryPath,
239    /// Class type of the entry
240    pub ctype: BinClassName,
241    /// Struct fields
242    pub fields: Vec<BinField>,
243}
244
245impl BinEntry {
246    /// Get a field by its name
247    pub fn get(&self, name: BinFieldName) -> Option<&BinField> {
248        self.fields.iter().find(|f| f.name == name)
249    }
250
251    /// Get a field by its name and downcast it
252    pub fn getv<T: BinValue + 'static>(&self, name: BinFieldName) -> Option<&T> {
253        self.get(name).and_then(|field| field.downcast::<T>())
254    }
255}
256
257/// Files known to not be PROP files, despite their extension
258pub const NON_PROP_BASENAMES: &[&str]  = &[
259    "atlas_info.bin",
260    "tftoutofgamecharacterdata.bin",
261    "tftmapcharacterlists.bin",
262    "tftactivesets.bin",
263    "tftitemlist.bin",
264];
265
266/// Return `true` if a path is a bin file path
267///
268/// This helper is intended to be used with files extracted by CDragon.
269/// It has several limitations.
270///
271/// - File content is not checked, only path is checkde
272/// - Some PROP files don't have the `.bin` extension, they will not be detected.
273///   (CDragon add the missing extension and thus does not have this problem.)
274/// - Some files have a `.bin` extension but are not actually PROP files.
275///   This helper return `false` for known occurrences.
276pub fn is_binfile_path(path: &Path) -> bool {
277    if let Some(true) = path.extension().map(|s| s == "bin") {
278        if let Some(name) = path.file_name() {
279            // Some files are not actual 'PROP' files
280            return name.to_str()
281                .map(|s| !NON_PROP_BASENAMES.contains(&s))
282                .unwrap_or(false)
283        }
284    }
285    false
286}
287
288
289/// Error in a PROP file
290#[allow(missing_docs)]
291#[derive(Error, Debug)]
292pub enum PropError {
293    #[error(transparent)]
294    Io(#[from] std::io::Error),
295    #[error("parsing error")]
296    Parsing(#[from] ParseError),
297}
298