1use crate::io_ext::{ReadExt, WriteExt};
2use std::fs::File;
3use std::io::{Read, Seek, SeekFrom, Write};
4use std::path::Path;
5
6use crate::common::M2Array;
7use crate::error::{M2Error, Result};
8use crate::version::M2Version;
9
10pub const SKIN_MAGIC: [u8; 4] = *b"SKIN";
12
13fn detect_skin_format<R: Read + Seek>(reader: &mut R) -> Result<bool> {
16 let start_pos = reader.stream_position()?;
17
18 reader.seek(SeekFrom::Current(4))?;
20
21 let second_field = reader.read_u32_le()?;
23
24 reader.seek(SeekFrom::Start(start_pos))?;
26
27 Ok(second_field <= 4)
30}
31
32pub fn parse_skin<R: Read + Seek>(reader: &mut R) -> Result<SkinFile> {
34 let is_new_format = detect_skin_format(reader)?;
35
36 if is_new_format {
37 let skin = SkinG::<SkinHeader>::parse(reader)?;
38 Ok(SkinFile::New(skin))
39 } else {
40 let skin = SkinG::<OldSkinHeader>::parse(reader)?;
41 Ok(SkinFile::Old(skin))
42 }
43}
44
45pub fn parse_embedded_skin<R: Read + Seek>(reader: &mut R, m2_version: u32) -> Result<SkinFile> {
47 let header = OldSkinHeader::parse_embedded(reader)?;
49
50 let mut indices = Vec::with_capacity(header.indices.count as usize);
52 if header.indices.count > 0 && header.indices.offset > 0 {
53 reader.seek(SeekFrom::Start(header.indices.offset as u64))?;
54 for _ in 0..header.indices.count {
55 indices.push(reader.read_u16_le()?);
56 }
57 }
58
59 let mut triangles = Vec::with_capacity(header.triangles.count as usize);
61 if header.triangles.count > 0 && header.triangles.offset > 0 {
62 reader.seek(SeekFrom::Start(header.triangles.offset as u64))?;
63 for _ in 0..header.triangles.count {
64 triangles.push(reader.read_u16_le()?);
65 }
66 }
67
68 let total_bone_bytes = (header.bone_indices.count as usize) * 4;
71 let mut bone_indices = Vec::with_capacity(total_bone_bytes);
72 if header.bone_indices.count > 0 && header.bone_indices.offset > 0 {
73 reader.seek(SeekFrom::Start(header.bone_indices.offset as u64))?;
74 for _ in 0..total_bone_bytes {
75 bone_indices.push(reader.read_u8()?);
76 }
77 }
78
79 let mut submeshes = Vec::with_capacity(header.submeshes.count as usize);
81 if header.submeshes.count > 0 && header.submeshes.offset > 0 {
82 reader.seek(SeekFrom::Start(header.submeshes.offset as u64))?;
83 for _ in 0..header.submeshes.count {
84 submeshes.push(SkinSubmesh::parse_with_version(reader, m2_version)?);
85 }
86 }
87
88 let mut batches = Vec::with_capacity(header.batches.count as usize);
90 if header.batches.count > 0 && header.batches.offset > 0 {
91 reader.seek(SeekFrom::Start(header.batches.offset as u64))?;
92 for _ in 0..header.batches.count {
93 batches.push(SkinBatch::parse(reader)?);
94 }
95 }
96
97 let skin = SkinG::<OldSkinHeader> {
98 header,
99 indices,
100 triangles,
101 bone_indices,
102 submeshes,
103 batches,
104 };
105
106 Ok(SkinFile::Old(skin))
107}
108
109pub fn load_skin<P: AsRef<Path>>(path: P) -> Result<SkinFile> {
111 let mut file = File::open(path)?;
112 parse_skin(&mut file)
113}
114
115pub trait SkinHeaderT: Sized {
116 fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self>;
117 fn write<W: Write>(&self, writer: &mut W) -> Result<()>;
118 fn calculate_size(&self) -> usize;
119 fn set_array_fields(
120 &mut self,
121 indices: M2Array<u16>,
122 triangles: M2Array<u16>,
123 bone_indices: M2Array<u8>,
124 submeshes: M2Array<SkinSubmesh>,
125 batches: M2Array<SkinBatch>,
126 );
127 fn indices(&self) -> &M2Array<u16>;
128 fn triangles(&self) -> &M2Array<u16>;
129 fn bone_indices(&self) -> &M2Array<u8>;
130 fn submeshes(&self) -> &M2Array<SkinSubmesh>;
131 fn batches(&self) -> &M2Array<SkinBatch>;
132}
133
134#[derive(Debug, Clone)]
136pub struct SkinHeader {
137 pub magic: [u8; 4],
139 pub version: u32,
141 pub name: M2Array<u8>,
143 pub vertex_count: u32,
145 pub indices: M2Array<u16>,
147 pub triangles: M2Array<u16>,
149 pub bone_indices: M2Array<u8>,
151 pub submeshes: M2Array<SkinSubmesh>,
153 pub batches: M2Array<SkinBatch>,
155 pub center_position: Option<[f32; 3]>,
157 pub center_bounds: Option<f32>,
159}
160
161impl SkinHeaderT for SkinHeader {
162 fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
164 let mut magic = [0u8; 4];
166 reader.read_exact(&mut magic)?;
167
168 if magic != SKIN_MAGIC {
169 return Err(M2Error::InvalidMagic {
170 expected: String::from_utf8_lossy(&SKIN_MAGIC).to_string(),
171 actual: String::from_utf8_lossy(&magic).to_string(),
172 });
173 }
174
175 let version = reader.read_u32_le()?;
177
178 if version > 4 {
180 return Err(M2Error::UnsupportedVersion(format!(
181 "New format version {} is too high, expected 0-4. This might be an old format file.",
182 version
183 )));
184 }
185
186 let _m2_version = match version {
188 0 => M2Version::Vanilla,
189 1 => M2Version::Cataclysm,
190 2 => M2Version::MoP,
191 3 => M2Version::WoD,
192 4 => M2Version::Legion,
193 v => {
194 return Err(M2Error::UnsupportedVersion(v.to_string()));
195 }
196 };
197
198 let name = M2Array::parse(reader)?;
200
201 let vertex_count = reader.read_u32_le()?;
203
204 let indices = M2Array::parse(reader)?;
206 let triangles = M2Array::parse(reader)?;
207 let bone_indices = M2Array::parse(reader)?;
208 let submeshes = M2Array::parse(reader)?;
209 let batches = M2Array::parse(reader)?;
210
211 let (center_position, center_bounds) = if version >= 4 {
213 let file_size = reader.seek(SeekFrom::End(0))?;
214
215 if file_size > reader.stream_position()? {
217 let mut center_pos = [0.0; 3];
218 for item in &mut center_pos {
219 *item = reader.read_f32_le()?;
220 }
221 let center_bound = reader.read_f32_le()?;
222
223 (Some(center_pos), Some(center_bound))
224 } else {
225 (None, None)
226 }
227 } else {
228 (None, None)
229 };
230
231 Ok(Self {
232 magic,
233 version,
234 name,
235 vertex_count,
236 indices,
237 triangles,
238 bone_indices,
239 submeshes,
240 batches,
241 center_position,
242 center_bounds,
243 })
244 }
245
246 fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
248 writer.write_all(&self.magic)?;
250 writer.write_u32_le(self.version)?;
251
252 self.name.write(writer)?;
254
255 writer.write_u32_le(self.vertex_count)?;
257
258 self.indices.write(writer)?;
260 self.triangles.write(writer)?;
261 self.bone_indices.write(writer)?;
262 self.submeshes.write(writer)?;
263 self.batches.write(writer)?;
264
265 if let Some(center_pos) = self.center_position {
267 for &value in ¢er_pos {
268 writer.write_f32_le(value)?;
269 }
270
271 if let Some(center_bound) = self.center_bounds {
272 writer.write_f32_le(center_bound)?;
273 } else {
274 writer.write_f32_le(0.0)?;
275 }
276 }
277
278 Ok(())
279 }
280
281 fn calculate_size(&self) -> usize {
283 let mut size = 4 + 4; size += 2 * 4;
287
288 size += 4;
290
291 size += 5 * (2 * 4); if self.center_position.is_some() {
296 size += 3 * 4; size += 4; }
299
300 size
301 }
302
303 fn set_array_fields(
304 &mut self,
305 indices: M2Array<u16>,
306 triangles: M2Array<u16>,
307 bone_indices: M2Array<u8>,
308 submeshes: M2Array<SkinSubmesh>,
309 batches: M2Array<SkinBatch>,
310 ) {
311 self.indices = indices;
312 self.triangles = triangles;
313 self.bone_indices = bone_indices;
314 self.submeshes = submeshes;
315 self.batches = batches;
316 }
317
318 fn indices(&self) -> &M2Array<u16> {
319 &self.indices
320 }
321
322 fn triangles(&self) -> &M2Array<u16> {
323 &self.triangles
324 }
325
326 fn bone_indices(&self) -> &M2Array<u8> {
327 &self.bone_indices
328 }
329
330 fn submeshes(&self) -> &M2Array<SkinSubmesh> {
331 &self.submeshes
332 }
333
334 fn batches(&self) -> &M2Array<SkinBatch> {
335 &self.batches
336 }
337}
338
339impl SkinHeader {
340 pub fn get_m2_version(&self) -> Option<M2Version> {
342 match self.version {
343 0 => Some(M2Version::Vanilla),
344 1 => Some(M2Version::Cataclysm),
345 2 => Some(M2Version::MoP),
346 3 => Some(M2Version::WoD),
347 4 => {
348 if self.center_position.is_some() {
350 Some(M2Version::BfA)
351 } else {
352 Some(M2Version::Legion)
353 }
354 }
355 _ => None,
356 }
357 }
358
359 pub fn new(m2_version: M2Version) -> Self {
361 let version = match m2_version {
362 M2Version::Vanilla | M2Version::TBC | M2Version::WotLK => 0,
363 M2Version::Cataclysm => 1,
364 M2Version::MoP => 2,
365 M2Version::WoD => 3,
366 M2Version::Legion => 4,
367 M2Version::BfA
368 | M2Version::Shadowlands
369 | M2Version::Dragonflight
370 | M2Version::TheWarWithin => 4,
371 };
372
373 let center_position = if m2_version >= M2Version::BfA {
374 Some([0.0, 0.0, 0.0])
375 } else {
376 None
377 };
378
379 let center_bounds = if m2_version >= M2Version::BfA {
380 Some(0.0)
381 } else {
382 None
383 };
384
385 Self {
386 magic: SKIN_MAGIC,
387 version,
388 name: M2Array::new(0, 0),
389 vertex_count: 0,
390 indices: M2Array::new(0, 0),
391 triangles: M2Array::new(0, 0),
392 bone_indices: M2Array::new(0, 0),
393 submeshes: M2Array::new(0, 0),
394 batches: M2Array::new(0, 0),
395 center_position,
396 center_bounds,
397 }
398 }
399}
400
401#[derive(Debug, Clone)]
403pub struct OldSkinHeader {
404 pub magic: [u8; 4],
406 pub indices: M2Array<u16>,
408 pub triangles: M2Array<u16>,
410 pub bone_indices: M2Array<u8>,
413 pub submeshes: M2Array<SkinSubmesh>,
415 pub batches: M2Array<SkinBatch>,
417 pub bone_count_max: u32,
419}
420
421impl OldSkinHeader {
422 pub fn parse_embedded<R: Read + Seek>(reader: &mut R) -> Result<Self> {
424 let indices = M2Array::parse(reader)?;
427 let triangles = M2Array::parse(reader)?;
428 let bone_indices = M2Array::parse(reader)?;
429 let submeshes = M2Array::parse(reader)?;
430 let batches = M2Array::parse(reader)?;
431
432 Ok(Self {
433 magic: SKIN_MAGIC, indices,
435 triangles,
436 bone_indices,
437 submeshes,
438 batches,
439 bone_count_max: 0, })
441 }
442}
443
444impl SkinHeaderT for OldSkinHeader {
445 fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
447 let mut magic = [0u8; 4];
449 reader.read_exact(&mut magic)?;
450
451 if magic != SKIN_MAGIC {
452 return Err(M2Error::InvalidMagic {
453 expected: String::from_utf8_lossy(&SKIN_MAGIC).to_string(),
454 actual: String::from_utf8_lossy(&magic).to_string(),
455 });
456 }
457
458 let indices = M2Array::parse(reader)?;
460 let triangles = M2Array::parse(reader)?;
461 let bone_indices = M2Array::parse(reader)?;
462 let submeshes = M2Array::parse(reader)?;
463 let batches = M2Array::parse(reader)?;
464
465 let bone_count_max = reader.read_u32_le()?;
467
468 Ok(Self {
469 magic,
470 indices,
471 triangles,
472 bone_indices,
473 submeshes,
474 batches,
475 bone_count_max,
476 })
477 }
478
479 fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
481 writer.write_all(&self.magic)?;
483
484 self.indices.write(writer)?;
486 self.triangles.write(writer)?;
487 self.bone_indices.write(writer)?;
488 self.submeshes.write(writer)?;
489 self.batches.write(writer)?;
490
491 writer.write_u32_le(self.bone_count_max)?;
493
494 Ok(())
495 }
496
497 fn calculate_size(&self) -> usize {
499 let mut size = 4; size += 5 * (2 * 4); size += 4;
506
507 size
508 }
509
510 fn set_array_fields(
511 &mut self,
512 indices: M2Array<u16>,
513 triangles: M2Array<u16>,
514 bone_indices: M2Array<u8>,
515 submeshes: M2Array<SkinSubmesh>,
516 batches: M2Array<SkinBatch>,
517 ) {
518 self.indices = indices;
519 self.triangles = triangles;
520 self.bone_indices = bone_indices;
521 self.submeshes = submeshes;
522 self.batches = batches;
523 }
524
525 fn indices(&self) -> &M2Array<u16> {
526 &self.indices
527 }
528
529 fn triangles(&self) -> &M2Array<u16> {
530 &self.triangles
531 }
532
533 fn bone_indices(&self) -> &M2Array<u8> {
534 &self.bone_indices
535 }
536
537 fn submeshes(&self) -> &M2Array<SkinSubmesh> {
538 &self.submeshes
539 }
540
541 fn batches(&self) -> &M2Array<SkinBatch> {
542 &self.batches
543 }
544}
545
546impl OldSkinHeader {
547 pub fn new() -> Self {
549 Self {
550 magic: SKIN_MAGIC,
551 indices: M2Array::new(0, 0),
552 triangles: M2Array::new(0, 0),
553 bone_indices: M2Array::new(0, 0),
554 submeshes: M2Array::new(0, 0),
555 batches: M2Array::new(0, 0),
556 bone_count_max: 0,
557 }
558 }
559}
560
561impl Default for OldSkinHeader {
562 fn default() -> Self {
563 Self::new()
564 }
565}
566
567#[derive(Debug, Clone)]
569pub struct SkinSubmesh {
570 pub id: u16,
572 pub level: u16,
574 pub vertex_start: u16,
576 pub vertex_count: u16,
578 pub triangle_start: u16,
580 pub triangle_count: u16,
582 pub bone_count: u16,
584 pub bone_start: u16,
586 pub bone_influence: u16,
588 pub center: [f32; 3],
590 pub sort_center: [f32; 3],
592 pub bounding_radius: f32,
594}
595
596impl SkinSubmesh {
597 pub fn parse_with_version<R: Read>(reader: &mut R, m2_version: u32) -> Result<Self> {
599 if m2_version < 260 {
600 Self::parse_vanilla(reader)
602 } else {
603 Self::parse(reader)
605 }
606 }
607
608 pub fn parse_vanilla<R: Read>(reader: &mut R) -> Result<Self> {
610 let id = reader.read_u16_le()?;
611 let level = reader.read_u16_le()?;
612 let vertex_start = reader.read_u16_le()?;
613 let vertex_count = reader.read_u16_le()?;
614 let triangle_start = reader.read_u16_le()?;
615 let triangle_count = reader.read_u16_le()?;
616 let bone_count = reader.read_u16_le()?;
617 let bone_start = reader.read_u16_le()?;
618
619 let float1 = reader.read_f32_le()?;
621 let float2 = reader.read_f32_le()?;
622 let float3 = reader.read_f32_le()?;
623 let float4 = reader.read_f32_le()?;
624
625 let center = [float1, float2, float3];
627
628 Ok(Self {
629 id,
630 level,
631 vertex_start,
632 vertex_count,
633 triangle_start,
634 triangle_count,
635 bone_count,
636 bone_start,
637 bone_influence: 0, center,
639 sort_center: [0.0, 0.0, 0.0], bounding_radius: float4, })
642 }
643
644 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
646 let id = reader.read_u16_le()?;
647 let level = reader.read_u16_le()?;
648 let vertex_start = reader.read_u16_le()?;
649 let vertex_count = reader.read_u16_le()?;
650 let triangle_start = reader.read_u16_le()?;
651 let triangle_count = reader.read_u16_le()?;
652 let bone_count = reader.read_u16_le()?;
653 let bone_start = reader.read_u16_le()?;
654 let bone_influence = reader.read_u16_le()?;
655
656 reader.read_u16_le()?;
658
659 let mut center = [0.0; 3];
660 let mut sort_center = [0.0; 3];
661
662 for item in &mut center {
663 *item = reader.read_f32_le()?;
664 }
665
666 for item in &mut sort_center {
667 *item = reader.read_f32_le()?;
668 }
669
670 let bounding_radius = reader.read_f32_le()?;
671
672 Ok(Self {
673 id,
674 level,
675 vertex_start,
676 vertex_count,
677 triangle_start,
678 triangle_count,
679 bone_count,
680 bone_start,
681 bone_influence,
682 center,
683 sort_center,
684 bounding_radius,
685 })
686 }
687
688 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
690 writer.write_u16_le(self.id)?;
691 writer.write_u16_le(self.level)?;
692 writer.write_u16_le(self.vertex_start)?;
693 writer.write_u16_le(self.vertex_count)?;
694 writer.write_u16_le(self.triangle_start)?;
695 writer.write_u16_le(self.triangle_count)?;
696 writer.write_u16_le(self.bone_count)?;
697 writer.write_u16_le(self.bone_start)?;
698 writer.write_u16_le(self.bone_influence)?;
699
700 writer.write_u16_le(0)?;
702
703 for &value in &self.center {
704 writer.write_f32_le(value)?;
705 }
706
707 for &value in &self.sort_center {
708 writer.write_f32_le(value)?;
709 }
710
711 writer.write_f32_le(self.bounding_radius)?;
712
713 Ok(())
714 }
715}
716
717#[derive(Debug, Clone)]
719pub struct SkinG<H>
720where
721 H: SkinHeaderT,
722{
723 pub header: H,
725 pub indices: Vec<u16>,
727 pub triangles: Vec<u16>,
729 pub bone_indices: Vec<u8>,
731 pub submeshes: Vec<SkinSubmesh>,
733 pub batches: Vec<SkinBatch>,
735}
736
737impl<H> SkinG<H>
738where
739 H: SkinHeaderT + Clone,
740{
741 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
743 let header = H::parse(reader)?;
745
746 let header_indices = header.indices();
748 reader.seek(SeekFrom::Start(header_indices.offset as u64))?;
749 let mut indices = Vec::with_capacity(header_indices.count as usize);
750 for _ in 0..header_indices.count {
751 indices.push(reader.read_u16_le()?);
752 }
753
754 let header_triangles = header.triangles();
756 reader.seek(SeekFrom::Start(header_triangles.offset as u64))?;
757 let mut triangles = Vec::with_capacity(header_triangles.count as usize);
758 for _ in 0..header_triangles.count {
759 triangles.push(reader.read_u16_le()?);
760 }
761
762 let header_bone_indices = header.bone_indices();
766 reader.seek(SeekFrom::Start(header_bone_indices.offset as u64))?;
767 let total_bone_bytes = (header_bone_indices.count as usize) * 4;
768 let mut bone_indices = Vec::with_capacity(total_bone_bytes);
769 for _ in 0..total_bone_bytes {
770 bone_indices.push(reader.read_u8()?);
771 }
772
773 let header_submeshes = header.submeshes();
775 reader.seek(SeekFrom::Start(header_submeshes.offset as u64))?;
776 let mut submeshes = Vec::with_capacity(header_submeshes.count as usize);
777 for _ in 0..header_submeshes.count {
778 submeshes.push(SkinSubmesh::parse(reader)?);
779 }
780
781 let header_batches = header.batches();
783 reader.seek(SeekFrom::Start(header_batches.offset as u64))?;
784 let mut batches = Vec::with_capacity(header_batches.count as usize);
785 for _ in 0..header_batches.count {
786 batches.push(SkinBatch::parse(reader)?);
787 }
788
789 Ok(Self {
790 header,
791 indices,
792 triangles,
793 bone_indices,
794 submeshes,
795 batches,
796 })
797 }
798
799 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
801 let mut file = File::open(path)?;
802 Self::parse(&mut file)
803 }
804
805 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
807 let mut file = File::create(path)?;
808 self.write(&mut file)
809 }
810
811 pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
813 let mut data_section = Vec::new();
815 let mut header = self.header.clone();
816
817 let header_size = header.calculate_size();
819 let mut current_offset = header_size as u32;
820
821 let indices = if !self.indices.is_empty() {
823 let indices = M2Array::new(self.indices.len() as u32, current_offset);
824
825 for &index in &self.indices {
826 data_section.extend_from_slice(&index.to_le_bytes());
827 }
828
829 current_offset += (self.indices.len() * std::mem::size_of::<u16>()) as u32;
830 indices
831 } else {
832 M2Array::new(0, 0)
833 };
834
835 let triangles = if !self.triangles.is_empty() {
837 let triangles = M2Array::new(self.triangles.len() as u32, current_offset);
838
839 for &triangle in &self.triangles {
840 data_section.extend_from_slice(&triangle.to_le_bytes());
841 }
842
843 current_offset += (self.triangles.len() * std::mem::size_of::<u16>()) as u32;
844
845 triangles
846 } else {
847 M2Array::new(0, 0)
848 };
849
850 let bone_indices = if !self.bone_indices.is_empty() {
853 let vertex_count = (self.bone_indices.len() / 4) as u32;
855 let bone_indices = M2Array::new(vertex_count, current_offset);
856
857 for &bone_index in &self.bone_indices {
858 data_section.push(bone_index);
859 }
860
861 current_offset += self.bone_indices.len() as u32;
862
863 bone_indices
864 } else {
865 M2Array::new(0, 0)
866 };
867
868 let submeshes = if !self.submeshes.is_empty() {
870 let submeshes = M2Array::new(self.submeshes.len() as u32, current_offset);
871
872 for submesh in &self.submeshes {
873 let mut submesh_data = Vec::new();
874 submesh.write(&mut submesh_data)?;
875 data_section.extend_from_slice(&submesh_data);
876 }
877
878 current_offset += (self.submeshes.len() * 40) as u32; submeshes
880 } else {
881 M2Array::new(0, 0)
882 };
883
884 let batches = if !self.batches.is_empty() {
886 let batches = M2Array::new(self.batches.len() as u32, current_offset);
887
888 for material in &self.batches {
889 let mut material_data = Vec::new();
890 material.write(&mut material_data)?;
891 data_section.extend_from_slice(&material_data);
892 }
893
894 batches
896 } else {
897 M2Array::new(0, 0)
898 };
899
900 header.set_array_fields(indices, triangles, bone_indices, submeshes, batches);
901
902 header.write(writer)?;
904 writer.write_all(&data_section)?;
905
906 Ok(())
907 }
908}
909
910impl SkinG<SkinHeader> {
911 pub fn convert(&self, target_version: M2Version) -> Result<Self> {
913 let source_version = self
914 .header
915 .get_m2_version()
916 .ok_or(M2Error::ConversionError {
917 from: self.header.version,
918 to: target_version.to_header_version(),
919 reason: "Unknown source version".to_string(),
920 })?;
921
922 if source_version == target_version {
923 return Ok(self.clone());
924 }
925
926 let mut new_skin = self.clone();
928
929 let mut header = SkinHeader::new(target_version);
931 header.name = self.header.name;
932 header.vertex_count = self.header.vertex_count;
933
934 if target_version >= M2Version::BfA && source_version < M2Version::BfA {
936 if header.center_position.is_none() {
938 let mut center = [0.0, 0.0, 0.0];
940 let mut max_radius = 0.0;
941
942 if !self.submeshes.is_empty() {
943 for submesh in &self.submeshes {
944 for (i, center_val) in center.iter_mut().enumerate() {
945 *center_val += submesh.center[i];
946 }
947
948 if submesh.bounding_radius > max_radius {
949 max_radius = submesh.bounding_radius;
950 }
951 }
952
953 let count = self.submeshes.len() as f32;
955 for item in &mut center {
956 *item /= count;
957 }
958 }
959
960 header.center_position = Some(center);
961 header.center_bounds = Some(max_radius);
962 }
963 } else if target_version < M2Version::BfA && source_version >= M2Version::BfA {
964 header.center_position = None;
966 header.center_bounds = None;
967 }
968
969 new_skin.header = header;
970
971 Ok(new_skin)
972 }
973
974 pub fn to_old_format(&self) -> OldSkin {
979 let bone_count_max = self
981 .submeshes
982 .iter()
983 .map(|s| s.bone_count as u32)
984 .max()
985 .unwrap_or(64);
986
987 OldSkin {
988 header: OldSkinHeader {
989 magic: SKIN_MAGIC,
990 indices: self.header.indices,
991 triangles: self.header.triangles,
992 bone_indices: self.header.bone_indices,
993 submeshes: self.header.submeshes.clone(),
994 batches: self.header.batches.clone(),
995 bone_count_max,
996 },
997 indices: self.indices.clone(),
998 triangles: self.triangles.clone(),
999 bone_indices: self.bone_indices.clone(),
1000 submeshes: self.submeshes.clone(),
1001 batches: self.batches.clone(),
1002 }
1003 }
1004}
1005
1006impl SkinG<OldSkinHeader> {
1007 pub fn to_new_format(&self, target_version: M2Version) -> Skin {
1012 let mut header = SkinHeader::new(target_version);
1013 header.indices = self.header.indices;
1014 header.triangles = self.header.triangles;
1015 header.bone_indices = self.header.bone_indices;
1016 header.submeshes = self.header.submeshes.clone();
1017 header.batches = self.header.batches.clone();
1018
1019 if !self.indices.is_empty() {
1021 header.vertex_count = self.indices.iter().copied().max().unwrap_or(0) as u32 + 1;
1022 }
1023
1024 Skin {
1025 header,
1026 indices: self.indices.clone(),
1027 triangles: self.triangles.clone(),
1028 bone_indices: self.bone_indices.clone(),
1029 submeshes: self.submeshes.clone(),
1030 batches: self.batches.clone(),
1031 }
1032 }
1033}
1034
1035pub type Skin = SkinG<SkinHeader>;
1036pub type OldSkin = SkinG<OldSkinHeader>;
1037
1038#[derive(Debug, Clone)]
1040pub enum SkinFile {
1041 New(Skin),
1043 Old(OldSkin),
1045}
1046
1047impl SkinFile {
1048 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
1050 parse_skin(reader)
1051 }
1052
1053 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
1055 load_skin(path)
1056 }
1057
1058 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
1060 match self {
1061 SkinFile::New(skin) => skin.save(path),
1062 SkinFile::Old(skin) => skin.save(path),
1063 }
1064 }
1065
1066 pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
1068 match self {
1069 SkinFile::New(skin) => skin.write(writer),
1070 SkinFile::Old(skin) => skin.write(writer),
1071 }
1072 }
1073
1074 pub fn get_resolved_indices(&self) -> Vec<u16> {
1085 self.triangles().clone()
1088 }
1089
1090 pub fn indices(&self) -> &Vec<u16> {
1097 match self {
1098 SkinFile::New(skin) => &skin.indices,
1099 SkinFile::Old(skin) => &skin.indices,
1100 }
1101 }
1102
1103 pub fn triangles(&self) -> &Vec<u16> {
1105 match self {
1106 SkinFile::New(skin) => &skin.triangles,
1107 SkinFile::Old(skin) => &skin.triangles,
1108 }
1109 }
1110
1111 pub fn submeshes(&self) -> &Vec<SkinSubmesh> {
1113 match self {
1114 SkinFile::New(skin) => &skin.submeshes,
1115 SkinFile::Old(skin) => &skin.submeshes,
1116 }
1117 }
1118
1119 pub fn batches(&self) -> &Vec<SkinBatch> {
1121 match self {
1122 SkinFile::New(skin) => &skin.batches,
1123 SkinFile::Old(skin) => &skin.batches,
1124 }
1125 }
1126
1127 pub fn convert(&self, target_version: M2Version) -> Result<Self> {
1136 let uses_new_format = target_version.uses_new_skin_format();
1137
1138 match (self, uses_new_format) {
1139 (SkinFile::New(skin), true) => {
1141 let converted = skin.convert(target_version)?;
1142 Ok(SkinFile::New(converted))
1143 }
1144
1145 (SkinFile::New(skin), false) => {
1147 let old_skin = skin.to_old_format();
1148 Ok(SkinFile::Old(old_skin))
1149 }
1150
1151 (SkinFile::Old(skin), true) => {
1153 let new_skin = skin.to_new_format(target_version);
1154 Ok(SkinFile::New(new_skin))
1155 }
1156
1157 (SkinFile::Old(skin), false) => Ok(SkinFile::Old(skin.clone())),
1159 }
1160 }
1161
1162 pub fn bone_indices(&self) -> &Vec<u8> {
1164 match self {
1165 SkinFile::New(skin) => &skin.bone_indices,
1166 SkinFile::Old(skin) => &skin.bone_indices,
1167 }
1168 }
1169
1170 pub fn is_new_format(&self) -> bool {
1172 matches!(self, SkinFile::New(_))
1173 }
1174
1175 pub fn is_old_format(&self) -> bool {
1177 matches!(self, SkinFile::Old(_))
1178 }
1179}
1180
1181#[derive(Debug, Clone)]
1182pub struct SkinBatch {
1183 pub flags: u8,
1184 pub priority_plane: i8,
1185 pub shader_id: u16,
1186 pub skin_section_index: u16,
1188 pub geoset_index: u16,
1190 pub color_index: u16,
1192 pub material_index: u16,
1194 pub material_layer: u16,
1196 pub texture_count: u16,
1197 pub texture_combo_index: u16,
1199 pub texture_coord_combo_index: u16,
1201 pub texture_weight_combo_index: u16,
1203 pub texture_transform_combo_index: u16,
1205}
1206
1207impl SkinBatch {
1208 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
1210 let flags = reader.read_u8()?;
1211 let priority_lane = reader.read_i8()?;
1212 let shader_id = reader.read_u16_le()?;
1213 let skin_section_index = reader.read_u16_le()?;
1214 let geoset_index = reader.read_u16_le()?;
1215 let color_index = reader.read_u16_le()?;
1216 let material_index = reader.read_u16_le()?;
1217 let material_layer = reader.read_u16_le()?;
1218 let texture_count = reader.read_u16_le()?;
1219 let texture_combo_index = reader.read_u16_le()?;
1220 let texture_coord_combo_index = reader.read_u16_le()?;
1221 let texture_weight_combo_index = reader.read_u16_le()?;
1222 let texture_transform_combo_index = reader.read_u16_le()?;
1223 Ok(Self {
1224 flags,
1225 priority_plane: priority_lane,
1226 shader_id,
1227 skin_section_index,
1228 geoset_index,
1229 color_index,
1230 material_index,
1231 material_layer,
1232 texture_count,
1233 texture_combo_index,
1234 texture_coord_combo_index,
1235 texture_weight_combo_index,
1236 texture_transform_combo_index,
1237 })
1238 }
1239
1240 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1242 writer.write_u8(self.flags)?;
1243 writer.write_i8(self.priority_plane)?;
1244 writer.write_u16_le(self.shader_id)?;
1245 writer.write_u16_le(self.skin_section_index)?;
1246 writer.write_u16_le(self.geoset_index)?;
1247 writer.write_u16_le(self.color_index)?;
1248 writer.write_u16_le(self.material_index)?;
1249 writer.write_u16_le(self.material_layer)?;
1250 writer.write_u16_le(self.texture_count)?;
1251 writer.write_u16_le(self.texture_combo_index)?;
1252 writer.write_u16_le(self.texture_coord_combo_index)?;
1253 writer.write_u16_le(self.texture_weight_combo_index)?;
1254 writer.write_u16_le(self.texture_transform_combo_index)?;
1255 Ok(())
1256 }
1257}
1258
1259#[cfg(test)]
1260mod tests {
1261 use super::*;
1262 use std::io::Cursor;
1263
1264 #[test]
1265 fn test_format_detection() {
1266 let mut data = Vec::new();
1268 data.extend_from_slice(&SKIN_MAGIC);
1269 data.extend_from_slice(&1u32.to_le_bytes()); let mut cursor = Cursor::new(&data);
1272 let is_new = detect_skin_format(&mut cursor).unwrap();
1273 assert!(is_new, "Version 1 should be detected as new format");
1274
1275 let mut data = Vec::new();
1277 data.extend_from_slice(&SKIN_MAGIC);
1278 data.extend_from_slice(&5903u32.to_le_bytes()); let mut cursor = Cursor::new(&data);
1281 let is_new = detect_skin_format(&mut cursor).unwrap();
1282 assert!(
1283 !is_new,
1284 "Large indices count should be detected as old format"
1285 );
1286
1287 let mut data = Vec::new();
1289 data.extend_from_slice(&SKIN_MAGIC);
1290 data.extend_from_slice(&4u32.to_le_bytes()); let mut cursor = Cursor::new(&data);
1293 let is_new = detect_skin_format(&mut cursor).unwrap();
1294 assert!(is_new, "Version 4 should be detected as new format");
1295
1296 let mut data = Vec::new();
1298 data.extend_from_slice(&SKIN_MAGIC);
1299 data.extend_from_slice(&5u32.to_le_bytes()); let mut cursor = Cursor::new(&data);
1302 let is_new = detect_skin_format(&mut cursor).unwrap();
1303 assert!(!is_new, "Indices count 5 should be detected as old format");
1304 }
1305
1306 #[test]
1307 fn test_skin_header_parse() {
1308 let mut data = Vec::new();
1309
1310 data.extend_from_slice(&SKIN_MAGIC);
1312
1313 data.extend_from_slice(&0u32.to_le_bytes());
1315
1316 data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&100u32.to_le_bytes());
1322
1323 data.extend_from_slice(&200u32.to_le_bytes()); data.extend_from_slice(&0x100u32.to_le_bytes()); data.extend_from_slice(&300u32.to_le_bytes()); data.extend_from_slice(&0x200u32.to_le_bytes()); data.extend_from_slice(&50u32.to_le_bytes()); data.extend_from_slice(&0x300u32.to_le_bytes()); data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&0x400u32.to_le_bytes()); data.extend_from_slice(&5u32.to_le_bytes()); data.extend_from_slice(&0x500u32.to_le_bytes()); let mut cursor = Cursor::new(data);
1344 let header = SkinHeader::parse(&mut cursor).unwrap();
1345
1346 assert_eq!(header.magic, SKIN_MAGIC);
1347 assert_eq!(header.version, 0);
1348 assert_eq!(header.vertex_count, 100);
1349 assert_eq!(header.indices.count, 200);
1350 assert_eq!(header.indices.offset, 0x100);
1351 assert_eq!(header.triangles.count, 300);
1352 assert_eq!(header.triangles.offset, 0x200);
1353 assert_eq!(header.bone_indices.count, 50);
1354 assert_eq!(header.bone_indices.offset, 0x300);
1355 assert_eq!(header.submeshes.count, 2);
1356 assert_eq!(header.submeshes.offset, 0x400);
1357 assert_eq!(header.batches.count, 5);
1358 assert_eq!(header.batches.offset, 0x500);
1359 assert!(header.center_position.is_none());
1360 assert!(header.center_bounds.is_none());
1361 }
1362
1363 #[test]
1364 #[ignore] fn test_skin_file_api() {
1366 let new_format_data = create_new_format_test_data();
1368 let old_format_data = create_old_format_test_data();
1369
1370 let mut cursor = Cursor::new(&new_format_data);
1372 let is_new = detect_skin_format(&mut cursor).unwrap();
1373 assert!(is_new, "New format should be detected");
1374
1375 let mut cursor = Cursor::new(&old_format_data);
1376 let is_new = detect_skin_format(&mut cursor).unwrap();
1377 assert!(!is_new, "Old format should be detected");
1378
1379 let mut cursor = Cursor::new(new_format_data);
1381 let skin_file = SkinFile::parse(&mut cursor).unwrap();
1382 assert!(skin_file.is_new_format());
1383 assert!(!skin_file.is_old_format());
1384
1385 let mut cursor = Cursor::new(old_format_data);
1387 let skin_file = SkinFile::parse(&mut cursor).unwrap();
1388 assert!(!skin_file.is_new_format());
1389 assert!(skin_file.is_old_format());
1390
1391 let indices = skin_file.indices();
1393 let submeshes = skin_file.submeshes();
1394 assert_eq!(indices.len(), 3); assert_eq!(submeshes.len(), 0); }
1397
1398 fn create_new_format_test_data() -> Vec<u8> {
1399 let mut data = Vec::new();
1400
1401 data.extend_from_slice(&SKIN_MAGIC);
1403
1404 data.extend_from_slice(&1u32.to_le_bytes());
1406
1407 data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&100u32.to_le_bytes());
1413
1414 let indices_offset = (4 + 4 + 8 + 4 + 5 * 8) as u32; data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&indices_offset.to_le_bytes()); for _ in 0..4 {
1421 data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); }
1424
1425 data.extend_from_slice(&10u16.to_le_bytes());
1427 data.extend_from_slice(&20u16.to_le_bytes());
1428 data.extend_from_slice(&30u16.to_le_bytes());
1429
1430 data
1431 }
1432
1433 fn create_old_format_test_data() -> Vec<u8> {
1434 let mut data = Vec::new();
1435
1436 data.extend_from_slice(&SKIN_MAGIC);
1438
1439 let indices_offset = (4 + 5 * 8) as u32; data.extend_from_slice(&3u32.to_le_bytes()); data.extend_from_slice(&indices_offset.to_le_bytes()); for _ in 0..4 {
1446 data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); }
1449
1450 data.extend_from_slice(&10u16.to_le_bytes());
1452 data.extend_from_slice(&20u16.to_le_bytes());
1453 data.extend_from_slice(&30u16.to_le_bytes());
1454
1455 data
1456 }
1457
1458 #[test]
1459 fn test_submesh_parse_write() {
1460 let submesh = SkinSubmesh {
1461 id: 1,
1462 level: 0,
1463 vertex_start: 0,
1464 vertex_count: 100,
1465 triangle_start: 0,
1466 triangle_count: 50,
1467 bone_count: 10,
1468 bone_start: 0,
1469 bone_influence: 4,
1470 center: [1.0, 2.0, 3.0],
1471 sort_center: [1.5, 2.5, 3.5],
1472 bounding_radius: 5.0,
1473 };
1474
1475 let mut data = Vec::new();
1476 submesh.write(&mut data).unwrap();
1477
1478 let mut cursor = Cursor::new(data);
1479 let parsed_submesh = SkinSubmesh::parse(&mut cursor).unwrap();
1480
1481 assert_eq!(parsed_submesh.id, 1);
1482 assert_eq!(parsed_submesh.vertex_count, 100);
1483 assert_eq!(parsed_submesh.triangle_count, 50);
1484 assert_eq!(parsed_submesh.bone_count, 10);
1485 assert_eq!(parsed_submesh.bone_influence, 4);
1486 assert_eq!(parsed_submesh.center, [1.0, 2.0, 3.0]);
1487 assert_eq!(parsed_submesh.sort_center, [1.5, 2.5, 3.5]);
1488 assert_eq!(parsed_submesh.bounding_radius, 5.0);
1489 }
1490
1491 #[test]
1492 fn test_skin_format_version_detection() {
1493 use crate::M2Version;
1494
1495 assert!(!M2Version::Vanilla.uses_new_skin_format());
1497 assert!(!M2Version::TBC.uses_new_skin_format());
1498 assert!(!M2Version::WotLK.uses_new_skin_format());
1499
1500 assert!(M2Version::Cataclysm.uses_new_skin_format());
1502 assert!(M2Version::MoP.uses_new_skin_format());
1503 assert!(M2Version::WoD.uses_new_skin_format());
1504 assert!(M2Version::Legion.uses_new_skin_format());
1505 }
1506
1507 #[test]
1508 fn test_cross_format_conversion_new_to_old() {
1509 use crate::M2Version;
1510
1511 let new_skin = Skin {
1513 header: SkinHeader::new(M2Version::Cataclysm),
1514 indices: vec![0, 1, 2, 3, 4],
1515 triangles: vec![0, 1, 2, 1, 2, 3],
1516 bone_indices: vec![0, 1],
1517 submeshes: vec![SkinSubmesh {
1518 id: 0,
1519 level: 0,
1520 vertex_start: 0,
1521 vertex_count: 5,
1522 triangle_start: 0,
1523 triangle_count: 6,
1524 bone_count: 2,
1525 bone_start: 0,
1526 bone_influence: 2,
1527 center: [0.0, 0.0, 0.0],
1528 sort_center: [0.0, 0.0, 0.0],
1529 bounding_radius: 1.0,
1530 }],
1531 batches: vec![],
1532 };
1533
1534 let old_skin = new_skin.to_old_format();
1536
1537 assert_eq!(old_skin.indices, new_skin.indices);
1539 assert_eq!(old_skin.triangles, new_skin.triangles);
1540 assert_eq!(old_skin.bone_indices, new_skin.bone_indices);
1541 assert_eq!(old_skin.submeshes.len(), new_skin.submeshes.len());
1542 }
1543
1544 #[test]
1545 fn test_cross_format_conversion_old_to_new() {
1546 use crate::M2Version;
1547 use crate::common::M2Array;
1548
1549 let old_skin = OldSkin {
1551 header: OldSkinHeader {
1552 magic: SKIN_MAGIC,
1553 indices: M2Array::new(5, 0),
1554 triangles: M2Array::new(6, 0),
1555 bone_indices: M2Array::new(2, 0),
1556 submeshes: M2Array::new(1, 0),
1557 batches: M2Array::new(0, 0),
1558 bone_count_max: 64,
1559 },
1560 indices: vec![0, 1, 2, 3, 4],
1561 triangles: vec![0, 1, 2, 1, 2, 3],
1562 bone_indices: vec![0, 1],
1563 submeshes: vec![SkinSubmesh {
1564 id: 0,
1565 level: 0,
1566 vertex_start: 0,
1567 vertex_count: 5,
1568 triangle_start: 0,
1569 triangle_count: 6,
1570 bone_count: 2,
1571 bone_start: 0,
1572 bone_influence: 2,
1573 center: [0.0, 0.0, 0.0],
1574 sort_center: [0.0, 0.0, 0.0],
1575 bounding_radius: 1.0,
1576 }],
1577 batches: vec![],
1578 };
1579
1580 let new_skin = old_skin.to_new_format(M2Version::Cataclysm);
1582
1583 assert_eq!(new_skin.indices, old_skin.indices);
1585 assert_eq!(new_skin.triangles, old_skin.triangles);
1586 assert_eq!(new_skin.bone_indices, old_skin.bone_indices);
1587 assert_eq!(new_skin.submeshes.len(), old_skin.submeshes.len());
1588 assert_eq!(new_skin.header.version, 1); }
1590
1591 #[test]
1592 fn test_skinfile_convert_cataclysm_to_wotlk() {
1593 use crate::M2Version;
1594
1595 let cata_skin = Skin {
1597 header: SkinHeader::new(M2Version::Cataclysm),
1598 indices: vec![0, 1, 2],
1599 triangles: vec![0, 1, 2],
1600 bone_indices: vec![0],
1601 submeshes: vec![],
1602 batches: vec![],
1603 };
1604
1605 let skin_file = SkinFile::New(cata_skin);
1606
1607 let converted = skin_file.convert(M2Version::WotLK).unwrap();
1609
1610 assert!(converted.is_old_format());
1612 assert!(!converted.is_new_format());
1613 }
1614
1615 #[test]
1616 fn test_skinfile_convert_wotlk_to_cataclysm() {
1617 use crate::M2Version;
1618 use crate::common::M2Array;
1619
1620 let wotlk_skin = OldSkin {
1622 header: OldSkinHeader {
1623 magic: SKIN_MAGIC,
1624 indices: M2Array::new(3, 0),
1625 triangles: M2Array::new(3, 0),
1626 bone_indices: M2Array::new(1, 0),
1627 submeshes: M2Array::new(0, 0),
1628 batches: M2Array::new(0, 0),
1629 bone_count_max: 64,
1630 },
1631 indices: vec![0, 1, 2],
1632 triangles: vec![0, 1, 2],
1633 bone_indices: vec![0],
1634 submeshes: vec![],
1635 batches: vec![],
1636 };
1637
1638 let skin_file = SkinFile::Old(wotlk_skin);
1639
1640 let converted = skin_file.convert(M2Version::Cataclysm).unwrap();
1642
1643 assert!(converted.is_new_format());
1645 assert!(!converted.is_old_format());
1646 }
1647}