1mod decoder;
2mod encoder;
3
4#[cfg(feature = "bevy_reflect")]
5use bevy_reflect::prelude::*;
6use bitflags::bitflags;
7use glam::{I8Vec3, U8Vec2};
8use serde::{Deserialize, Serialize};
9
10pub use decoder::{DecodeError, Decoder};
11pub use encoder::{EncodeError, Encoder};
12
13bitflags! {
14 #[repr(transparent)]
15 #[derive(Clone, Copy, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
16 #[cfg_attr(feature = "debug", derive(Debug))]
17 #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(opaque), reflect(Default, Deserialize, Hash, PartialEq, Serialize))]
18 #[cfg_attr(all(feature = "bevy_reflect", feature = "debug"), reflect(Debug))]
19 pub struct HeadFlags: u8 {
20 const NONE = 0;
21 const NO_MOUTH = 1 << 0;
24 const HIDE_ACCESSORY_0_IN_MEET = 1 << 1;
27 const HIDE_ACCESSORY_1_IN_MEET = 1 << 2;
30 const HIDE_HEAD_ACCESSORY_IN_MEET = 1 << 3;
32 const NO_INJURED_VARIANT = 1 << 4;
35 const NO_DEATH_VARIANT = 1 << 5;
38 const UNKNOWN_HEAD_FLAG_6 = 1 << 6;
39 const UNKNOWN_HEAD_FLAG_7 = 1 << 7;
40 }
41}
42
43#[derive(Clone, Default, Deserialize, Serialize)]
44#[cfg_attr(feature = "debug", derive(Debug))]
45#[cfg_attr(
46 feature = "bevy_reflect",
47 derive(Reflect),
48 reflect(Default, Deserialize, Serialize)
49)]
50#[cfg_attr(all(feature = "bevy_reflect", feature = "debug"), reflect(Debug))]
51pub struct HeadsDatabase {
52 pub entries: Vec<HeadEntry>,
53}
54
55#[derive(Clone, Default, Deserialize, Serialize)]
56#[cfg_attr(feature = "debug", derive(Debug))]
57#[cfg_attr(
58 feature = "bevy_reflect",
59 derive(Reflect),
60 reflect(Default, Deserialize, Serialize)
61)]
62#[cfg_attr(all(feature = "bevy_reflect", feature = "debug"), reflect(Debug))]
63pub struct HeadEntry {
64 pub name: String,
67 pub flags: HeadFlags,
69 pub battle_sequences_id: u8,
72 pub meet_sequences_id: u8,
75 pub mouth: Option<Mouth>,
76 pub eyes: Option<Eyes>,
77 body: ModelSlot,
79 head: ModelSlot,
81 pub battle_keyframes_id: u8,
84 pub meet_keyframes_id: u8,
87 neck: ModelSlot,
89 accessories: [ModelSlot; 2],
92 head_accessory: ModelSlot,
94}
95
96impl HeadEntry {
97 pub fn body(&self) -> Option<ModelSlot> {
98 if self.body.model_id == 0 {
99 None
100 } else {
101 Some(self.body.clone())
102 }
103 }
104
105 pub fn head(&self) -> Option<ModelSlot> {
106 if self.head.model_id == 0 {
107 None
108 } else {
109 Some(self.head.clone())
110 }
111 }
112
113 pub fn head_accessory(&self) -> Option<ModelSlot> {
114 if self.head_accessory.model_id == 0 {
115 None
116 } else {
117 Some(self.head_accessory.clone())
118 }
119 }
120
121 pub fn neck(&self) -> Option<ModelSlot> {
122 if self.neck.model_id == 0 {
123 None
124 } else {
125 Some(self.neck.clone())
126 }
127 }
128
129 pub fn accessories(&self) -> [Option<ModelSlot>; 2] {
130 [
131 if self.accessories[0].model_id == 0 {
132 None
133 } else {
134 Some(self.accessories[0].clone())
135 },
136 if self.accessories[1].model_id == 0 {
137 None
138 } else {
139 Some(self.accessories[1].clone())
140 },
141 ]
142 }
143}
144
145#[derive(Clone, Default, Deserialize, Serialize)]
146#[cfg_attr(feature = "debug", derive(Debug))]
147#[cfg_attr(
148 feature = "bevy_reflect",
149 derive(Reflect),
150 reflect(Default, Deserialize, Serialize)
151)]
152#[cfg_attr(all(feature = "bevy_reflect", feature = "debug"), reflect(Debug))]
153pub struct ModelSlot {
154 pub model_id: u8,
156 pub translation: I8Vec3,
159}
160
161#[derive(Clone, Default, Deserialize, Serialize)]
162#[cfg_attr(feature = "debug", derive(Debug))]
163#[cfg_attr(
164 feature = "bevy_reflect",
165 derive(Reflect),
166 reflect(Default, Deserialize, Serialize)
167)]
168#[cfg_attr(all(feature = "bevy_reflect", feature = "debug"), reflect(Debug))]
169pub struct Mouth {
170 pub size: U8Vec2,
173 pub position: U8Vec2,
175}
176
177#[derive(Clone, Default, Deserialize, Serialize)]
178#[cfg_attr(feature = "debug", derive(Debug))]
179#[cfg_attr(
180 feature = "bevy_reflect",
181 derive(Reflect),
182 reflect(Default, Deserialize, Serialize)
183)]
184#[cfg_attr(all(feature = "bevy_reflect", feature = "debug"), reflect(Debug))]
185pub struct Eyes {
186 pub size: U8Vec2,
189 pub position: U8Vec2,
191}
192
193#[cfg(test)]
194mod tests {
195 use std::{
196 ffi::{OsStr, OsString},
197 fs::File,
198 path::{Path, PathBuf},
199 };
200
201 use pretty_assertions::assert_eq;
202
203 use super::*;
204
205 fn roundtrip_test(original_bytes: &[u8], heads: &HeadsDatabase) {
206 let mut encoded_bytes = Vec::new();
207 Encoder::new(&mut encoded_bytes).encode(heads).unwrap();
208
209 let original_bytes = original_bytes
210 .chunks(16)
211 .map(|chunk| {
212 chunk
213 .iter()
214 .map(|b| format!("{b:02X}"))
215 .collect::<Vec<_>>()
216 .join(" ")
217 })
218 .collect::<Vec<_>>()
219 .join("\n");
220
221 let encoded_bytes = encoded_bytes
222 .chunks(16)
223 .map(|chunk| {
224 chunk
225 .iter()
226 .map(|b| format!("{b:02X}"))
227 .collect::<Vec<_>>()
228 .join(" ")
229 })
230 .collect::<Vec<_>>()
231 .join("\n");
232
233 assert_eq!(original_bytes, encoded_bytes);
234 }
235
236 #[test]
237 fn test_decode_heads_db() {
238 let d: PathBuf = [
239 std::env::var("DARKOMEN_PATH").unwrap().as_str(),
240 "DARKOMEN",
241 "GRAPHICS",
242 "PORTRAIT",
243 "SCRIPT",
244 "HEADS.DB",
245 ]
246 .iter()
247 .collect();
248
249 let original_bytes = std::fs::read(d.clone()).unwrap();
250 let file = File::open(d).unwrap();
251 let heads = Decoder::new(file).decode().unwrap();
252
253 assert_eq!(heads.entries.len(), 63);
254 assert_eq!(heads.entries.first().unwrap().name, "MB");
255 assert_eq!(
256 heads.entries.first().unwrap().flags,
257 HeadFlags::HIDE_ACCESSORY_0_IN_MEET | HeadFlags::HIDE_ACCESSORY_1_IN_MEET
258 );
259 assert_eq!(heads.entries.first().unwrap().body.model_id, 2);
260 assert_eq!(heads.entries.first().unwrap().head.model_id, 13);
261
262 roundtrip_test(&original_bytes, &heads);
263 }
264
265 #[test]
266 fn test_encode_too_many_entries() {
267 let heads = HeadsDatabase {
268 entries: vec![HeadEntry::default(); 256],
269 };
270
271 let mut encoded_bytes = Vec::new();
272 let result = Encoder::new(&mut encoded_bytes).encode(&heads);
273
274 assert!(result.is_err());
275 match result {
276 Err(EncodeError::TooManyEntries) => (),
277 _ => panic!("Expected TooManyEntries error"),
278 }
279 }
280
281 #[test]
282 fn test_decode_all() {
283 let d: PathBuf = [
284 std::env::var("DARKOMEN_PATH").unwrap().as_str(),
285 "DARKOMEN",
286 "GRAPHICS",
287 "PORTRAIT",
288 ]
289 .iter()
290 .collect();
291
292 let root_output_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "decoded", "portrait", "heads"]
293 .iter()
294 .collect();
295
296 std::fs::create_dir_all(&root_output_dir).unwrap();
297
298 fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&Path)) {
299 println!("Reading dir {:?}", dir.display());
300
301 let mut paths = std::fs::read_dir(dir)
302 .unwrap()
303 .map(|res| res.map(|e| e.path()))
304 .collect::<Result<Vec<_>, std::io::Error>>()
305 .unwrap();
306
307 paths.sort();
308
309 for path in paths {
310 if path.is_dir() {
311 visit_dirs(&path, cb);
312 } else {
313 cb(&path);
314 }
315 }
316 }
317
318 visit_dirs(&d, &mut |path| {
319 let Some(ext) = path.extension() else {
320 return;
321 };
322 if ext.to_string_lossy().to_uppercase() != "DB" {
323 return;
324 }
325 if path.file_stem().unwrap().to_string_lossy() != "HEADS" {
329 return;
330 }
331
332 println!("Decoding {:?}", path.file_name().unwrap());
333
334 let original_bytes = std::fs::read(path).unwrap();
335
336 let file = File::open(path).unwrap();
337 let heads = Decoder::new(file).decode().unwrap();
338
339 roundtrip_test(&original_bytes, &heads);
340
341 let parent_dir = path
342 .components()
343 .collect::<Vec<_>>()
344 .iter()
345 .rev()
346 .skip(1) .take_while(|c| c.as_os_str() != "DARKOMEN")
348 .collect::<Vec<_>>()
349 .iter()
350 .rev()
351 .collect::<PathBuf>();
352 let output_dir = root_output_dir.join(parent_dir);
353 std::fs::create_dir_all(&output_dir).unwrap();
354
355 let output_path = append_ext("ron", output_dir.join(path.file_name().unwrap()));
357 let mut buffer = String::new();
358 ron::ser::to_writer_pretty(&mut buffer, &heads, Default::default()).unwrap();
359 std::fs::write(output_path, buffer).unwrap();
360
361 let db_name = path.file_stem().unwrap().to_string_lossy();
363 let individual_dir = output_dir.join(db_name.as_ref());
364 std::fs::create_dir_all(&individual_dir).unwrap();
365
366 for (index, entry) in heads.entries.iter().enumerate() {
367 let individual_path =
368 individual_dir.join(format!("{:02}_{}.ron", index, entry.name));
369 let mut buffer = String::new();
370 ron::ser::to_writer_pretty(&mut buffer, entry, Default::default()).unwrap();
371 std::fs::write(individual_path, buffer).unwrap();
372 }
373 });
374 }
375
376 fn append_ext(ext: impl AsRef<OsStr>, path: PathBuf) -> PathBuf {
377 let mut os_string: OsString = path.into();
378 os_string.push(".");
379 os_string.push(ext.as_ref());
380 os_string.into()
381 }
382}