1use std::path::{Path, PathBuf};
7
8use thiserror::Error;
9
10use rakata_core::fs::find_case_insensitive_file;
11use rakata_core::ResRef;
12use rakata_core::ResourceTypeCode;
13use rakata_formats::Key;
14
15use crate::keyfile::{KeyFile, KeyFileError};
16
17#[derive(Debug, Clone)]
19pub struct Chitin {
20 game_root: PathBuf,
21 key_file: KeyFile,
22}
23
24impl Chitin {
25 pub fn read_from_root(game_root: impl AsRef<Path>) -> Result<Self, ChitinError> {
27 let game_root = game_root.as_ref().to_path_buf();
28 let key_path = find_case_insensitive_file(&game_root, "chitin.key")
29 .ok()
30 .flatten()
31 .unwrap_or_else(|| game_root.join("chitin.key"));
32 let key_file = KeyFile::read_from_file_with_base(&key_path, &game_root)?;
33 Ok(Self {
34 game_root,
35 key_file,
36 })
37 }
38
39 pub fn game_root(&self) -> &Path {
41 &self.game_root
42 }
43
44 pub fn key_file(&self) -> &KeyFile {
46 &self.key_file
47 }
48
49 pub fn key(&self) -> &Key {
51 self.key_file.key()
52 }
53
54 pub fn resource(
56 &self,
57 resref: &ResRef,
58 resource_type: ResourceTypeCode,
59 ) -> Result<Option<Vec<u8>>, ChitinError> {
60 self.key_file
61 .resource(resref, resource_type)
62 .map_err(ChitinError::from)
63 }
64}
65
66#[derive(Debug, Error)]
68pub enum ChitinError {
69 #[error(transparent)]
71 KeyFile(#[from] KeyFileError),
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 use std::fs;
79
80 use rakata_core::{ResourceId, ResourceType};
81 use rakata_formats::{write_bif_to_vec, write_key_to_vec, Bif};
82 use tempfile::TempDir;
83
84 #[test]
85 fn loads_chitin_key_and_resolves_resource() {
86 let resource_id = ResourceId::from_parts(0, 0).expect("valid resource id");
87 let resource_type = ResourceTypeCode::from(ResourceType::Utc);
88
89 let mut bif = Bif::new();
90 bif.push_resource(resource_id, resource_type, b"utc".to_vec());
91 let bif_bytes = write_bif_to_vec(&bif).expect("write bif");
92
93 let mut key = Key::new();
94 key.push_bif_entry(
95 "data\\templates.bif",
96 u32::try_from(bif_bytes.len()).expect("test BIF size fits in u32"),
97 0,
98 );
99 key.push_resource(
100 ResRef::new("p_bastila").expect("valid resref"),
101 resource_type,
102 resource_id,
103 );
104 let key_bytes = write_key_to_vec(&key).expect("write key");
105
106 let temp = TempDir::new().expect("create tempdir");
107 let root = temp.path();
108 let data_dir = root.join("data");
109 fs::create_dir_all(&data_dir).expect("create data dir");
110 fs::write(data_dir.join("templates.bif"), bif_bytes).expect("write bif");
111 fs::write(root.join("chitin.key"), key_bytes).expect("write key");
112
113 let chitin = Chitin::read_from_root(root).expect("load chitin");
114 let resref = ResRef::new("p_bastila").expect("valid resref");
115 let data = chitin
116 .resource(&resref, resource_type)
117 .expect("resolve")
118 .expect("resource exists");
119 assert_eq!(data, b"utc");
120 }
121
122 #[test]
123 fn loads_case_insensitive_chitin_key_name() {
124 let resource_id = ResourceId::from_parts(0, 0).expect("valid resource id");
125 let resource_type = ResourceTypeCode::from(ResourceType::Utc);
126
127 let mut bif = Bif::new();
128 bif.push_resource(resource_id, resource_type, b"utc".to_vec());
129 let bif_bytes = write_bif_to_vec(&bif).expect("write bif");
130
131 let mut key = Key::new();
132 key.push_bif_entry(
133 "data\\templates.bif",
134 u32::try_from(bif_bytes.len()).expect("test BIF size fits in u32"),
135 0,
136 );
137 key.push_resource(
138 ResRef::new("p_bastila").expect("valid resref"),
139 resource_type,
140 resource_id,
141 );
142 let key_bytes = write_key_to_vec(&key).expect("write key");
143
144 let temp = TempDir::new().expect("create tempdir");
145 let root = temp.path();
146 let data_dir = root.join("data");
147 fs::create_dir_all(&data_dir).expect("create data dir");
148 fs::write(data_dir.join("templates.bif"), bif_bytes).expect("write bif");
149 fs::write(root.join("ChItIn.KeY"), key_bytes).expect("write key");
150
151 let chitin = Chitin::read_from_root(root).expect("load chitin");
152 let resref = ResRef::new("p_bastila").expect("valid resref");
153 let data = chitin
154 .resource(&resref, resource_type)
155 .expect("resolve")
156 .expect("resource exists");
157 assert_eq!(data, b"utc");
158 }
159}