1mod sub_ser;
3mod trait_impls;
4
5use crate::{align, lib::*, tri};
6
7use self::sub_ser::structs::StructSerializer;
8use self::trait_impls::{Align as _, ClassNamesWriter, ClassStartsMap};
9use super::serde::{hkx_header::HkxHeader, section_header::SectionHeader};
10use crate::errors::ser::{
11 Error, InvalidEndianSnafu, MissingClassInClassnamesSectionSnafu, MissingGlobalFixupClassSnafu,
12 Result, UnsupportedPtrSizeSnafu,
13};
14use byteorder::{BigEndian, ByteOrder, LittleEndian, WriteBytesExt as _};
15use havok_serde::ser::{Serialize, Serializer};
16use havok_types::*;
17use indexmap::IndexMap;
18use std::borrow::Cow;
19use std::collections::HashMap;
20use std::ffi::CString as StdCString;
21use std::io::{Cursor, Write as _};
22
23pub fn to_bytes<V>(value: &V, header: &HkxHeader) -> Result<Vec<u8>>
33where
34 V: Serialize + ClassNamesWriter,
35{
36 to_bytes_with_opt(
37 value,
38 header,
39 ByteSerializer {
40 is_little_endian: match header.endian {
41 0 => false, 1 => true, invalid => InvalidEndianSnafu { invalid }.fail()?,
44 },
45 is_x86: match header.pointer_size {
46 4 => true,
47 8 => false,
48 invalid => UnsupportedPtrSizeSnafu { invalid }.fail()?,
49 },
50 ..Default::default()
51 },
52 )
53}
54
55pub fn to_bytes_with_opt<V>(value: &V, header: &HkxHeader, ser: ByteSerializer) -> Result<Vec<u8>>
61where
62 V: Serialize + ClassNamesWriter,
63{
64 let mut serializer = ser;
65
66 serializer.output.write_all(&header.to_bytes())?;
68 serializer.contents_class_name_section_index = header.contents_class_name_section_index;
69 serializer.contents_section_index = header.contents_section_index;
70
71 let classnames_header_start = 64 + header.padding_size() as u64; let types_header_start = classnames_header_start + 48; let data_header_start = types_header_start + 48;
75 serializer.output.set_position(data_header_start + 48);
76
77 if serializer.is_little_endian {
80 serializer.class_starts =
81 value.write_classnames_section::<LittleEndian>(&mut serializer.output)?;
82 } else {
83 serializer.class_starts =
84 value.write_classnames_section::<BigEndian>(&mut serializer.output)?;
85 };
86 #[cfg(feature = "tracing")]
87 tracing::trace!("class_starts: {:#?}", serializer.class_starts);
88
89 serializer.abs_data_offset = header.padding_size() + serializer.output.position() as u32;
91 serializer.current_last_local_dst = serializer.abs_data_offset as u64;
92 value.serialize(&mut serializer)?;
93
94 serializer.output.zero_fill_align(16)?; let (local_offset, global_offset, virtual_offset) = serializer.write_data_fixups()?; let exports_offset = serializer.relative_position()?; let abs_data_start = serializer.abs_data_offset;
101 let data_section_header = SectionHeader {
102 section_tag: SectionHeader::DATA_SECTION_HEADER_TAG,
103 section_tag_separator: 0xff,
104 absolute_data_start: abs_data_start,
105 local_fixups_offset: local_offset,
106 global_fixups_offset: global_offset,
107 virtual_fixups_offset: virtual_offset,
108 exports_offset,
109 imports_offset: exports_offset,
110 end_offset: exports_offset,
111 };
112 #[cfg(feature = "tracing")]
113 tracing::trace!("data_section_header = {data_section_header}");
114
115 serializer.output.set_position(classnames_header_start);
116 let section_offset = header.section_offset;
117
118 if serializer.is_little_endian {
119 tri!(serializer.write_section_headers::<LittleEndian>(
120 section_offset,
121 types_header_start,
122 data_header_start,
123 &data_section_header,
124 ));
125 } else {
126 tri!(serializer.write_section_headers::<BigEndian>(
127 section_offset,
128 types_header_start,
129 data_header_start,
130 &data_section_header,
131 ));
132 };
133
134 Ok(serializer.output.into_inner())
135}
136
137#[derive(Debug, Default)]
142pub struct ByteSerializer {
143 is_little_endian: bool,
145 is_x86: bool,
148
149 output: Cursor<Vec<u8>>,
151
152 abs_data_offset: u32,
154
155 is_in_str_array: bool,
160
161 pointed_pos: Vec<u64>,
167 current_last_local_dst: u64,
169 local_fixups: Vec<u8>,
178
179 global_fixups_ptr_src: IndexMap<u32, Pointer>,
184 virtual_fixups_ptr_src: HashMap<Pointer, u32>,
195 contents_section_index: i32,
199
200 virtual_fixups: Vec<u8>,
205 class_starts: ClassStartsMap,
211 contents_class_name_section_index: i32,
215}
216
217impl ByteSerializer {
218 #[inline]
220 fn relative_position(&self) -> Result<u32> {
221 let position = self.output.position() as u32;
222 let abs_data_offset = self.abs_data_offset;
223 position
224 .checked_sub(abs_data_offset)
225 .ok_or(Error::OverflowSubtractAbs {
226 position,
227 abs_data_offset,
228 })
229 }
230
231 fn write_global_fixups(&mut self) -> Result<()> {
239 for (&src, ptr) in &self.global_fixups_ptr_src {
240 if let Some(&dst) = self.virtual_fixups_ptr_src.get(ptr) {
241 #[cfg(feature = "tracing")]
242 {
243 let src_abs = self.abs_data_offset + src;
244 let dst_abs = self.abs_data_offset + dst;
245 tracing::debug!(
246 "[global_fixups]({:#x}) src({src}:{src:#x}/abs {src_abs:#x}) -> {ptr} dst({dst}:{dst:#x}/abs: {dst_abs:#x})",
247 self.output.position()
248 );
249 }
250
251 if self.is_little_endian {
252 self.output.write_u32::<LittleEndian>(src)?; self.output
254 .write_i32::<LittleEndian>(self.contents_section_index)?; self.output.write_u32::<LittleEndian>(dst)?; } else {
257 self.output.write_u32::<BigEndian>(src)?; self.output
259 .write_i32::<BigEndian>(self.contents_section_index)?; self.output.write_u32::<BigEndian>(dst)?; }
262 } else {
263 return MissingGlobalFixupClassSnafu { ptr: *ptr }.fail();
264 }
265 }
266 Ok(())
267 }
268
269 fn write_virtual_fixups_pair(
275 &mut self,
276 class_name: &'static str,
277 virtual_src: u32,
278 ) -> Result<()> {
279 if let Some(class_name_offset) = self.class_starts.get(class_name) {
280 if self.is_little_endian {
281 self.virtual_fixups.write_u32::<LittleEndian>(virtual_src)?; self.virtual_fixups
283 .write_i32::<LittleEndian>(self.contents_class_name_section_index)?; self.virtual_fixups
285 .write_u32::<LittleEndian>(*class_name_offset)?; } else {
287 self.virtual_fixups.write_u32::<BigEndian>(virtual_src)?; self.virtual_fixups
289 .write_i32::<BigEndian>(self.contents_class_name_section_index)?; self.virtual_fixups
291 .write_u32::<BigEndian>(*class_name_offset)?; };
293 Ok(())
294 } else {
295 MissingClassInClassnamesSectionSnafu { class_name }.fail()
296 }
297 }
298
299 fn write_data_fixups(&mut self) -> Result<(u32, u32, u32)> {
304 let local_offset = self.relative_position()?;
305 self.output.write_all(&self.local_fixups)?;
306 self.output.align(16, 0xff)?;
307
308 #[cfg(feature = "tracing")]
309 tracing::debug!(
310 "[global_fixups pointers]\nsrc: {:#?},\ndest(same as virtual.src): {:#?}",
311 self.global_fixups_ptr_src,
312 self.virtual_fixups_ptr_src
313 );
314 let global_offset = self.relative_position()?;
315 self.write_global_fixups()?;
316 self.output.align(16, 0xff)?;
317
318 let virtual_offset = self.relative_position()?;
319 self.output.write_all(&self.virtual_fixups)?;
320 self.output.align(16, 0xff)?;
321 Ok((local_offset, global_offset, virtual_offset))
322 }
323
324 #[inline]
326 fn write_section_headers<B>(
327 &mut self,
328 section_offset: i16,
329 types_header_start: u64,
330 data_header_start: u64,
331 data_section_header: &SectionHeader,
332 ) -> Result<()>
333 where
334 B: ByteOrder,
335 {
336 SectionHeader::write_classnames::<B>(
338 &mut self.output,
339 section_offset,
340 self.abs_data_offset,
341 )?;
342 self.output.set_position(types_header_start);
344 SectionHeader::write_types::<B>(&mut self.output, self.abs_data_offset)?;
345 self.output.set_position(data_header_start);
347 data_section_header.write_bytes::<B>(&mut self.output)?;
348 Ok(())
349 }
350
351 #[inline]
354 fn goto_local_dst(&mut self) -> Result<u32> {
355 let &dest_abs_pos = tri!(
356 self.pointed_pos
357 .last()
358 .ok_or(Error::NotFoundPointedPosition)
359 );
360 self.output.set_position(dest_abs_pos);
361 self.relative_position()
362 }
363
364 #[inline]
366 fn write_local_fixup_pair(&mut self, local_src: u32, local_dst: u32) -> Result<()> {
367 #[cfg(feature = "tracing")]
368 {
369 let src_abs = self.abs_data_offset + local_src;
370 let dst_abs = self.abs_data_offset + local_dst;
371 tracing::debug!(
372 "[local_fixup] src({local_src}/abs: {src_abs:#x}), dst({local_dst}/abs: {dst_abs:#x})"
373 );
374 }
375 if self.is_little_endian {
376 self.local_fixups.write_u32::<LittleEndian>(local_src)?;
377 self.local_fixups.write_u32::<LittleEndian>(local_dst)?;
378 } else {
379 self.local_fixups.write_u32::<BigEndian>(local_src)?;
380 self.local_fixups.write_u32::<BigEndian>(local_dst)?;
381 }
382 Ok(())
383 }
384
385 fn serialize_cow(&mut self, v: &Option<Cow<'_, str>>) -> Result<()> {
387 #[cfg(feature = "tracing")]
388 tracing::trace!("pointed_pos:({:#x?})", self.pointed_pos);
389
390 if let Some(v) = v {
392 let ptr_start = self.relative_position()?;
393 tri!(self.serialize_ulong(Ulong::new(0))); let next_ser_pos = self.output.position();
395
396 #[cfg(feature = "tracing")]
397 tracing::trace!("Serialize `CString`/`StringPtr` ({next_ser_pos:#x}): (\"{v}\")",);
398
399 let pointed_pos = tri!(self.goto_local_dst());
401 tri!(self.write_local_fixup_pair(ptr_start, pointed_pos));
402
403 let c_string = StdCString::new(v.as_bytes())?;
404 let _ = self.output.write(c_string.as_bytes_with_nul())?;
405 if self.is_in_str_array {
406 self.output.zero_fill_align(2)?;
407 } else {
408 self.output.zero_fill_align(16)?;
409 }
410
411 let next_pointed_ser_pos = self.output.position();
412 self.current_last_local_dst = next_pointed_ser_pos;
413 if let Some(last) = self.pointed_pos.last_mut() {
414 *last = next_pointed_ser_pos; };
416
417 self.output.set_position(next_ser_pos);
418 } else {
419 tri!(self.serialize_ulong(Ulong::new(0))); };
421 Ok(())
422 }
423}
424
425macro_rules! impl_serialize_primitive {
427 ($method:ident, $value_type:ty, $write:ident) => {
428 #[inline]
429 fn $method(self, v: $value_type) -> Result<Self::Ok, Self::Error> {
430 match self.is_little_endian {
431 true => self.output.$write::<LittleEndian>(v),
432 false => self.output.$write::<BigEndian>(v),
433 }?;
434 Ok(())
435 }
436 };
437}
438
439macro_rules! impl_serialize_math {
441 ($method:ident, $value_type:ty) => {
442 fn $method(self, v: &$value_type) -> Result<Self::Ok, Self::Error> {
443 match self.is_little_endian {
444 true => self.output.write(v.to_le_bytes().as_slice()),
445 false => self.output.write(v.to_be_bytes().as_slice()),
446 }?;
447 Ok(())
448 }
449 };
450}
451
452impl<'a> Serializer for &'a mut ByteSerializer {
453 type Ok = ();
454 type Error = Error;
455
456 type SerializeSeq = Self;
457 type SerializeStruct = StructSerializer<'a>;
458 type SerializeFlags = Self;
459
460 #[inline]
461 fn serialize_void(self, _: ()) -> Result<Self::Ok, Self::Error> {
462 Ok(())
463 }
464
465 #[inline]
466 fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
467 self.serialize_uint8(v as u8)
468 }
469
470 #[inline]
471 fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
473 self.serialize_int8(v as i8)
474 }
475
476 #[inline]
477 fn serialize_int8(self, v: i8) -> Result<Self::Ok, Self::Error> {
478 self.output.write_i8(v)?;
479 Ok(())
480 }
481
482 #[inline]
483 fn serialize_uint8(self, v: u8) -> Result<Self::Ok, Self::Error> {
484 self.output.write_u8(v)?;
485 Ok(())
486 }
487
488 impl_serialize_primitive!(serialize_int16, i16, write_i16);
489 impl_serialize_primitive!(serialize_uint16, u16, write_u16);
490
491 impl_serialize_primitive!(serialize_int32, i32, write_i32);
492 impl_serialize_primitive!(serialize_uint32, u32, write_u32);
493
494 impl_serialize_primitive!(serialize_int64, i64, write_i64);
495 impl_serialize_primitive!(serialize_uint64, u64, write_u64);
496
497 impl_serialize_primitive!(serialize_real, f32, write_f32);
498
499 impl_serialize_math!(serialize_vector4, Vector4);
500 impl_serialize_math!(serialize_quaternion, Quaternion);
501 impl_serialize_math!(serialize_matrix3, Matrix3);
502 impl_serialize_math!(serialize_rotation, Rotation);
503 impl_serialize_math!(serialize_matrix4, Matrix4);
504 impl_serialize_math!(serialize_qstransform, QsTransform);
505 impl_serialize_math!(serialize_transform, Transform);
506
507 fn serialize_pointer(self, ptr: Pointer) -> Result<Self::Ok, Self::Error> {
510 #[allow(clippy::needless_else)]
511 if !ptr.is_null() {
512 let start = self.relative_position()?;
514 #[cfg(feature = "tracing")]
515 tracing::debug!(
516 "Insert `global_fixup.src`({start}:{start:#x}/abs {:#x}): {ptr}",
517 self.output.position()
518 );
519 self.global_fixups_ptr_src.insert(start, ptr);
520 } else {
521 #[cfg(feature = "tracing")]
522 tracing::debug!("Skip global_fixup.src writing, because it's null ptr.");
523 };
524 self.serialize_ulong(Ulong::new(0))
525 }
526
527 #[inline]
528 fn serialize_array(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
529 Ok(self)
530 }
531
532 fn serialize_struct(
539 self,
540 name: &'static str,
541 class_meta: Option<(Pointer, Signature)>,
542 sizes: (u64, u64),
543 ) -> Result<Self::SerializeStruct, Self::Error> {
544 let size = if self.is_x86 { sizes.0 } else { sizes.1 };
545
546 #[allow(clippy::needless_else)]
547 let is_root = if let Some((ptr, _sig)) = class_meta {
548 self.output.zero_fill_align(16)?; let virtual_fixup_abs = self.output.position();
551 #[cfg(feature = "tracing")]
552 tracing::debug!(
553 "serialize struct {name}(index = {ptr}, signature = {_sig}, abs_position = {virtual_fixup_abs:#x})"
554 );
555
556 let virtual_src = self.relative_position()?;
557 self.write_virtual_fixups_pair(name, virtual_src)?; self.virtual_fixups_ptr_src.insert(ptr, virtual_src); let last_local_dst = align!(virtual_fixup_abs + size, 16_u64);
562 self.current_last_local_dst = last_local_dst;
563 self.pointed_pos.push(last_local_dst);
564 true
565 } else {
566 #[cfg(feature = "tracing")]
570 tracing::debug!("serialize struct {name}(A class within a field.)");
571 false
572 };
573
574 Ok(Self::SerializeStruct::new(self, is_root))
575 }
576
577 #[inline]
578 fn serialize_variant(self, v: &Variant) -> Result<Self::Ok, Self::Error> {
579 self.serialize_pointer(v.object)?;
580 self.serialize_pointer(v.class)
581 }
582
583 #[inline]
584 fn serialize_cstring(self, v: &CString) -> Result<Self::Ok, Self::Error> {
585 self.serialize_cow(v.get_ref())
586 }
587
588 #[inline]
589 fn serialize_ulong(self, v: Ulong) -> Result<Self::Ok, Self::Error> {
590 match self.is_x86 {
591 true => self.serialize_uint32(v.get() as u32),
592 false => self.serialize_uint64(v.get()),
593 }
594 }
595
596 #[inline]
597 fn serialize_enum_flags(self) -> Result<Self::SerializeFlags, Self::Error> {
598 Ok(self)
599 }
600
601 #[inline]
602 fn serialize_half(self, v: f16) -> Result<Self::Ok, Self::Error> {
603 let bytes = if self.is_little_endian {
604 v.to_le_bytes()
605 } else {
606 v.to_be_bytes()
607 };
608 self.output.write_all(&bytes)?;
609 Ok(())
610 }
611
612 #[inline]
615 fn serialize_stringptr(self, v: &StringPtr) -> Result<Self::Ok, Self::Error> {
616 self.serialize_cow(v.get_ref())
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use crate::{HavokSort as _, bytes::hexdump, tests::mocks::new_defaultmale};
624 use havok_classes::{
625 BlendCurve, EndMode, EventMode, FlagBits, hkbBlendingTransitionEffect, hkbGenerator,
626 hkbModifierGenerator,
627 };
628 fn partial_parse_assert<T>(s: T, expected: &[u8], ser: Option<ByteSerializer>)
631 where
632 T: Serialize + PartialEq,
633 {
634 let mut ser = ser.unwrap_or(ByteSerializer {
635 is_little_endian: true,
636 ..Default::default()
637 });
638 match <T as Serialize>::serialize(&s, &mut ser) {
639 Ok(_) => assert_eq!(ser.output.into_inner(), expected),
640 Err(err) => {
641 tracing::error!(?err);
642 panic!("{err}")
643 }
644 }
645 }
646
647 #[test]
648 fn test_serialize_primitive() {
649 assert_eq!(FlagBits::empty().bits(), 0);
650 assert_eq!(FlagBits::from_bits_retain(0).bits(), 0);
651 partial_parse_assert(FlagBits::empty().bits(), &[0, 0], None);
652 partial_parse_assert(FlagBits::empty(), &[0, 0], None);
653 partial_parse_assert(FlagBits::FLAG_SYNC, &[2, 0], None);
654 partial_parse_assert(EventMode::EVENT_MODE_DEFAULT, &[0], None);
655 }
656
657 #[test]
658 fn test_serialize_hkb_blending_transition_effect() {
659 #[rustfmt::skip]
660 let expected = &[
6610, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128,
6640, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
665
6660, 0, 128, 63,
6670, 0, 128, 63,
6681, 0, 2, 1,
6710,
6720, 0, 0, 0, 0, 0, 0, 0,
6730, 0, 0, 0, 0, 0, 0, 0,
6740, 0, 0, 0, 0, 0, 0, 0,
6750, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128,
6760, 0, 0, 64, 0, 0, 128, 63, 1,
6791, 0, 0, 0, 0, 0, 0 ];
682
683 partial_parse_assert(
684 hkbBlendingTransitionEffect {
685 __ptr: None,
686 parent: Default::default(), m_duration: 1.0, m_toGeneratorStartTimeFraction: 1.0, m_flags: FlagBits::FLAG_IGNORE_FROM_WORLD_FROM_MODEL, m_endMode: EndMode::END_MODE_CAP_DURATION_AT_END_OF_FROM_GENERATOR, m_blendCurve: BlendCurve::BLEND_CURVE_LINEAR, m_fromGenerator: Pointer::new(0), m_toGenerator: Pointer::new(0), m_characterPoseAtBeginningOfTransition: Vec::new(), m_timeRemaining: 2.0, m_timeInTransition: 1.0, m_applySelfTransition: true, m_initializeCharacterPose: true, },
701 expected,
702 None,
703 );
704 }
705
706 #[test]
707 fn test_serialize_hkb_modifier_generator() {
709 let ser = ByteSerializer {
710 class_starts: {
711 let mut class_starts = IndexMap::new();
712 class_starts.insert("hkbModifierGenerator", 0);
713 class_starts
714 },
715 is_little_endian: true,
716 ..Default::default()
717 };
718
719 let parent_parent_parent: [u8; 48] = [
721 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
722 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0,
723 ];
724 #[rustfmt::skip]
726 let parent_parent = [
7271, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
734
735 #[rustfmt::skip]
737 let current = [
7380, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
741 #[rustfmt::skip]
742 let string_ptr = [
7430, 0, 0, 0, 0, 0, 0, 0, 78, 111, 100, 101, 78, 97, 109, 101, 0, 0, 0, 0, 0, 0, 0, 0, ];
747
748 const ARRAY_LEN: usize = 88 + 24;
749 let mut expected: [u8; ARRAY_LEN] = [0; ARRAY_LEN];
750 expected[0..48].clone_from_slice(&parent_parent_parent);
751 expected[48..72].clone_from_slice(&parent_parent);
752 expected[72..88].clone_from_slice(¤t);
754 expected[88..ARRAY_LEN].clone_from_slice(&string_ptr);
755
756 partial_parse_assert(
757 hkbModifierGenerator {
758 __ptr: Some(Pointer::new(1)), parent: hkbGenerator {
760 __ptr: None,
761 parent: havok_classes::hkbNode {
762 __ptr: None,
763 parent: Default::default(),
764 m_userData: Ulong::new(1),
765 m_name: "NodeName".into(),
766 m_id: 0,
767 m_cloneState: 0,
768 m_padNode: [false],
769 },
770 },
771 m_modifier: Pointer::new(0),
772 m_generator: Pointer::new(0),
773 },
774 &expected,
775 Some(ser),
776 );
777 }
778
779 #[test]
780 fn test_serialize() -> Result<()> {
781 let mut classes = new_defaultmale();
782 classes.sort_for_bytes();
783 tracing::debug!("{classes:#?}");
784
785 let bytes = to_bytes(&classes, &HkxHeader::new_skyrim_se())?;
786 let actual = hexdump::to_string(&bytes);
787 tracing::debug!("\n{actual}");
788
789 let expected = hexdump::to_string(include_bytes!(
790 "../../../../docs/handson_hex_dump/defaultmale/defaultmale.hkx"
791 ));
792 pretty_assertions::assert_eq!(actual, expected);
793 Ok(())
794 }
795}