xc3_lib/bc/
skel.rs

1use crate::{align, parse_offset64_count32, parse_opt_ptr64, parse_ptr64, parse_string_ptr64};
2use binrw::{binread, BinRead};
3use xc3_write::{
4    strings::{StringSectionUniqueSorted, WriteOptions},
5    Xc3Write, Xc3WriteOffsets,
6};
7
8use super::{BcList, StringOffset, Transform};
9
10#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
11#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
12#[br(magic(b"SKEL"))]
13#[xc3(magic(b"SKEL"))]
14pub struct Skel {
15    #[br(parse_with = parse_ptr64)]
16    #[xc3(offset(u64))]
17    pub skeleton: Skeleton,
18}
19
20// TODO: variable size?
21// 160, 192, 224, 240
22
23#[binread]
24#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
25#[derive(Debug, Xc3Write, PartialEq, Clone)]
26#[br(stream = r)]
27pub struct Skeleton {
28    // Use temp fields to estimate the struct size.
29    // These fields will be skipped when writing.
30    // TODO: is there a better way to handle game specific differences?
31    #[br(temp, try_calc = r.stream_position())]
32    base_offset: u64,
33
34    pub unk1: BcList<u8>,
35    pub unk2: u64, // 0
36
37    #[br(parse_with = parse_string_ptr64)]
38    #[xc3(offset(u64))]
39    pub root_bone_name: String,
40
41    pub parent_indices: BcList<i16>,
42
43    pub names: BcList<BoneName>,
44
45    // Store the offset for the next field.
46    #[br(temp, restore_position)]
47    transforms_offset: u32,
48
49    #[br(parse_with = parse_offset64_count32)]
50    #[xc3(offset_count(u64, u32), align(16, 0xff))]
51    pub transforms: Vec<Transform>,
52    pub unk3: i32, // -1
53
54    #[br(parse_with = parse_offset64_count32)]
55    #[xc3(offset_count(u64, u32), align(8, 0xff))]
56    pub extra_track_slots: Vec<SkeletonExtraTrackSlot>,
57    pub unk4: i32, // -1
58
59    // MT_ or mount bones?
60    #[br(parse_with = parse_offset64_count32)]
61    #[xc3(offset_count(u64, u32), align(8, 0xff))]
62    pub mt_parent_indices: Vec<i16>,
63    pub unk5: i32, // -1
64
65    #[br(parse_with = parse_offset64_count32)]
66    #[xc3(offset_count(u64, u32), align(8, 0xff))]
67    pub mt_names: Vec<StringOffset>,
68    pub unk6: i32, // -1
69
70    #[br(parse_with = parse_offset64_count32)]
71    #[xc3(offset_count(u64, u32), align(16, 0xff))]
72    pub mt_transforms: Vec<Transform>,
73    pub unk7: i32, // -1
74
75    pub labels: BcList<SkeletonLabel>,
76
77    #[br(args_raw(transforms_offset as u64 - base_offset))]
78    pub extra: SkeletonExtra,
79}
80
81// TODO: Make this an option instead?
82
83// Up to 80 bytes of optional data for XC3.
84#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
85#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
86#[br(import_raw(size: u64))]
87pub enum SkeletonExtra {
88    #[br(pre_assert(size == 160))]
89    Unk0,
90
91    #[br(pre_assert(size == 192))]
92    Unk1(SkeletonExtraUnk1),
93
94    #[br(pre_assert(size == 224))]
95    Unk2(SkeletonExtraUnk2),
96
97    #[br(pre_assert(size == 240))]
98    Unk3(SkeletonExtraUnk3),
99
100    // TODO: extra variant?
101    Unk,
102}
103
104#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] // TODO: Fix writing.
105#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
106pub struct SkeletonExtraUnk1 {
107    #[br(parse_with = parse_opt_ptr64)]
108    #[xc3(offset(u64), align(16, 0xff))]
109    pub unk6: Option<SkeletonUnk6Unk1>,
110
111    #[br(parse_with = parse_opt_ptr64)]
112    #[xc3(offset(u64), align(16, 0xff))]
113    pub unk7: Option<SkeletonUnk7>,
114
115    #[br(parse_with = parse_opt_ptr64)]
116    #[xc3(offset(u64), align(16, 0xff))]
117    pub unk8: Option<SkeletonUnk8>,
118}
119
120#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
121#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
122pub struct SkeletonExtraUnk2 {
123    #[br(parse_with = parse_opt_ptr64)]
124    #[xc3(offset(u64), align(16, 0xff))]
125    pub unk6: Option<SkeletonUnk6>,
126
127    #[br(parse_with = parse_opt_ptr64)]
128    #[xc3(offset(u64), align(16, 0xff))]
129    pub unk7: Option<SkeletonUnk7>,
130
131    #[br(parse_with = parse_opt_ptr64)]
132    #[xc3(offset(u64), align(16, 0xff))]
133    pub unk8: Option<SkeletonUnk8>,
134
135    #[br(parse_with = parse_opt_ptr64)]
136    #[xc3(offset(u64), align(16, 0xff))]
137    pub unk9: Option<SkeletonUnk9>,
138
139    #[br(parse_with = parse_opt_ptr64)]
140    #[xc3(offset(u64), align(16, 0xff))]
141    pub unk10: Option<SkeletonUnk10>,
142
143    #[br(parse_with = parse_opt_ptr64)]
144    #[xc3(offset(u64), align(16, 0xff))]
145    pub unk11: Option<SkeletonUnk11>,
146
147    pub unk2: u64,
148    pub unk3: i64,
149}
150
151#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
152#[derive(Debug, BinRead, Xc3Write, PartialEq, Clone)]
153pub struct SkeletonExtraUnk3 {
154    #[br(parse_with = parse_opt_ptr64)]
155    #[xc3(offset(u64), align(16, 0xff))]
156    pub unk6: Option<SkeletonUnk6>,
157
158    #[br(parse_with = parse_opt_ptr64)]
159    #[xc3(offset(u64), align(16, 0xff))]
160    pub unk7: Option<SkeletonUnk7>,
161
162    #[br(parse_with = parse_opt_ptr64)]
163    #[xc3(offset(u64), align(16, 0xff))]
164    pub unk8: Option<SkeletonUnk8>,
165
166    #[br(parse_with = parse_opt_ptr64)]
167    #[xc3(offset(u64), align(16, 0xff))]
168    pub unk9: Option<SkeletonUnk9>,
169
170    #[br(parse_with = parse_opt_ptr64)]
171    #[xc3(offset(u64), align(16, 0xff))]
172    pub unk10: Option<SkeletonUnk10>,
173
174    #[br(parse_with = parse_opt_ptr64)]
175    #[xc3(offset(u64), align(16, 0xff))]
176    pub unk11: Option<SkeletonUnk11>,
177
178    #[br(parse_with = parse_opt_ptr64)]
179    #[xc3(offset(u64), align(8, 0xff))]
180    pub unk12: Option<SkeletonUnk12>,
181
182    #[br(parse_with = parse_opt_ptr64)]
183    #[xc3(offset(u64), align(8, 0xff))]
184    pub unk13: Option<SkeletonUnk13>,
185
186    pub unk2: u64,
187    pub unk3: i64,
188}
189
190#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
191#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
192pub struct SkeletonLabel {
193    pub bone_type: u32, // enum?
194    pub index: u16,     // incremented if type is the same?
195    pub bone_index: u16,
196}
197
198#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
199#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
200pub struct BoneName {
201    #[br(parse_with = parse_string_ptr64)]
202    #[xc3(offset(u64))]
203    pub name: String,
204
205    // TODO: padding?
206    pub unk: [u32; 2],
207}
208
209#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
210#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
211pub struct SkeletonExtraTrackSlot {
212    #[br(parse_with = parse_string_ptr64)]
213    #[xc3(offset(u64))]
214    pub unk1: String,
215
216    pub unk2: BcList<StringOffset>,
217
218    pub unk3: BcList<f32>,
219
220    #[br(parse_with = parse_offset64_count32)]
221    #[xc3(offset_count(u64, u32), align(8, 0xff))]
222    pub unk4: Vec<[f32; 2]>,
223    pub unk1_1: i32, // -1
224}
225
226#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
227#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
228pub struct SkeletonUnk6 {
229    pub unk1: BcList<u8>,
230
231    #[br(parse_with = parse_offset64_count32)]
232    #[xc3(offset_count(u64, u32), align(4, 0xff))]
233    pub unk2: Vec<u16>,
234    pub unk2_1: i32, // -1
235
236    #[br(parse_with = parse_offset64_count32)]
237    #[xc3(offset_count(u64, u32), align(8, 0xff))]
238    pub unk3: Vec<u32>,
239}
240
241#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
242#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
243pub struct SkeletonUnk6Unk1 {
244    pub unk1: BcList<u8>,
245
246    #[br(parse_with = parse_offset64_count32)]
247    #[xc3(offset_count(u64, u32), align(4, 0xff))]
248    pub unk2: Vec<u16>,
249    pub unk2_1: i32, // -1
250}
251
252#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
253#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
254pub struct SkeletonUnk7 {
255    pub unk1: BcList<u8>,
256
257    #[br(parse_with = parse_offset64_count32)]
258    #[xc3(offset_count(u64, u32), align(4, 0xff))]
259    pub unk2: Vec<u16>,
260    pub unk2_1: i32, // -1
261
262    // TODO: type?
263    #[br(parse_with = parse_offset64_count32)]
264    #[xc3(offset_count(u64, u32), align(8, 0xff))]
265    pub unk3: Vec<u32>,
266}
267
268#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
269#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
270pub struct SkeletonUnk8 {
271    #[br(parse_with = parse_offset64_count32)]
272    #[xc3(offset_count(u64, u32))]
273    pub unk1: Vec<u32>,
274}
275
276#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
277#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
278pub struct SkeletonUnk9 {
279    // TODO: type?
280    pub unk1: BcList<[u32; 13]>,
281
282    // TODO: type?
283    #[br(parse_with = parse_offset64_count32)]
284    #[xc3(offset_count(u64, u32))]
285    pub unk2: Vec<u64>,
286}
287
288#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
289#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
290pub struct SkeletonUnk10 {
291    // TODO: type?
292    pub unk1: [u32; 8],
293}
294
295#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
296#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
297pub struct SkeletonUnk11 {
298    #[br(parse_with = parse_offset64_count32)]
299    #[xc3(offset_count(u64, u32))]
300    pub unk1: Vec<u8>,
301}
302
303#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
304#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
305pub struct SkeletonUnk12 {
306    #[br(parse_with = parse_offset64_count32)]
307    #[xc3(offset_count(u64, u32))]
308    pub unk1: Vec<u16>,
309}
310
311#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
312#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone)]
313pub struct SkeletonUnk13 {
314    pub unk1: BcList<[f32; 4]>,
315    pub unk2: BcList<i16>,
316}
317
318impl Xc3WriteOffsets for SkeletonOffsets<'_> {
319    type Args = ();
320
321    fn write_offsets<W: std::io::prelude::Write + std::io::prelude::Seek>(
322        &self,
323        writer: &mut W,
324        base_offset: u64,
325        data_ptr: &mut u64,
326        endian: xc3_write::Endian,
327        _args: Self::Args,
328    ) -> xc3_write::Xc3Result<()> {
329        // The names are stored in a single section.
330        let mut string_section = StringSectionUniqueSorted::default();
331        string_section.insert_offset64(&self.root_bone_name);
332
333        // Different order than field order.
334        if !self.unk1.0.data.is_empty() {
335            self.unk1
336                .write_offsets(writer, base_offset, data_ptr, endian, ())?;
337        }
338        self.transforms
339            .write_full(writer, base_offset, data_ptr, endian, ())?;
340
341        let names = self.names.0.write(writer, base_offset, data_ptr, endian)?;
342        for name in names.0 {
343            string_section.insert_offset64(&name.name);
344        }
345
346        self.parent_indices
347            .write_offsets(writer, base_offset, data_ptr, endian, ())?;
348
349        if !self.extra_track_slots.data.is_empty() {
350            let slots = self
351                .extra_track_slots
352                .write(writer, base_offset, data_ptr, endian)?;
353            for slot in slots.0 {
354                string_section.insert_offset64(&slot.unk1);
355
356                if !slot.unk2.0.data.is_empty() {
357                    let names = slot.unk2.0.write(writer, base_offset, data_ptr, endian)?;
358                    for name in names.0 {
359                        string_section.insert_offset64(&name.name);
360                    }
361                }
362
363                if !slot.unk3.0.data.is_empty() {
364                    slot.unk3
365                        .write_offsets(writer, base_offset, data_ptr, endian, ())?;
366                }
367                if !slot.unk4.data.is_empty() {
368                    slot.unk4
369                        .write_full(writer, base_offset, data_ptr, endian, ())?;
370                }
371            }
372        }
373
374        if !self.mt_parent_indices.data.is_empty() {
375            self.mt_parent_indices
376                .write_full(writer, base_offset, data_ptr, endian, ())?;
377        }
378        if !self.mt_names.data.is_empty() {
379            let names = self.mt_names.write(writer, base_offset, data_ptr, endian)?;
380            for name in names.0 {
381                string_section.insert_offset64(&name.name);
382            }
383        }
384        if !self.mt_transforms.data.is_empty() {
385            self.mt_transforms
386                .write_full(writer, base_offset, data_ptr, endian, ())?;
387        }
388
389        // TODO: Only padded if MT data is not present?
390        if self.mt_parent_indices.data.is_empty() {
391            weird_skel_alignment(writer, data_ptr, endian)?;
392        }
393
394        if !self.labels.0.data.is_empty() {
395            self.labels
396                .write_offsets(writer, base_offset, data_ptr, endian, ())?;
397        }
398
399        self.extra
400            .write_offsets(writer, base_offset, data_ptr, endian, ())?;
401
402        // The names are the last item before the addresses.
403        let start_alignment = match self.extra {
404            SkeletonExtraOffsets::Unk0 => 4,
405            SkeletonExtraOffsets::Unk1(_) => 8,
406            SkeletonExtraOffsets::Unk2(_) => 8,
407            SkeletonExtraOffsets::Unk3(_) => 8,
408            SkeletonExtraOffsets::Unk => todo!(),
409        };
410        string_section.write(
411            writer,
412            data_ptr,
413            &WriteOptions {
414                start_alignment,
415                start_padding_byte: 0xff,
416                string_alignment: 1,
417                string_padding_byte: 0,
418            },
419            endian,
420        )?;
421
422        Ok(())
423    }
424}
425
426fn weird_skel_alignment<W: std::io::Write + std::io::Seek>(
427    writer: &mut W,
428    data_ptr: &mut u64,
429    endian: xc3_write::Endian,
430) -> xc3_write::Xc3Result<()> {
431    // TODO: What is this strange padding?
432    // First align to 8.
433    // FF...
434    let pos = writer.stream_position()?;
435    align(writer, pos, 8, 0xff)?;
436
437    // Now align to 16.
438    // 0000 FF...
439    [0u8; 2].xc3_write(writer, endian)?;
440    *data_ptr = (*data_ptr).max(writer.stream_position()?);
441
442    let pos = writer.stream_position()?;
443    align(writer, pos, 16, 0xff)?;
444
445    // 0000
446    [0u8; 4].xc3_write(writer, endian)?;
447    *data_ptr = (*data_ptr).max(writer.stream_position()?);
448    Ok(())
449}
450
451impl Xc3WriteOffsets for SkeletonExtraUnk3Offsets<'_> {
452    type Args = ();
453
454    fn write_offsets<W: std::io::prelude::Write + std::io::prelude::Seek>(
455        &self,
456        writer: &mut W,
457        base_offset: u64,
458        data_ptr: &mut u64,
459        endian: xc3_write::Endian,
460        _args: Self::Args,
461    ) -> xc3_write::Xc3Result<()> {
462        // Different order than field order.
463        self.unk6
464            .write_full(writer, base_offset, data_ptr, endian, ())?;
465        self.unk7
466            .write_full(writer, base_offset, data_ptr, endian, ())?;
467        self.unk12
468            .write_full(writer, base_offset, data_ptr, endian, ())?;
469        self.unk9
470            .write_full(writer, base_offset, data_ptr, endian, ())?;
471        self.unk8
472            .write_full(writer, base_offset, data_ptr, endian, ())?;
473        self.unk10
474            .write_full(writer, base_offset, data_ptr, endian, ())?;
475        self.unk11
476            .write_full(writer, base_offset, data_ptr, endian, ())?;
477        self.unk13
478            .write_full(writer, base_offset, data_ptr, endian, ())?;
479        Ok(())
480    }
481}