1use crate::io_ext::{ReadExt, WriteExt};
2use std::io::{Read, Seek, Write};
3
4use crate::common::M2ArrayString;
5use crate::error::Result;
6use crate::version::M2Version;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum M2TextureType {
11 Hardcoded = 0,
13 Body = 1,
15 Item = 2,
17 WeaponArmorBasic = 3,
19 WeaponBlade = 4,
21 WeaponHandle = 5,
23 Environment = 6,
25 Hair = 7,
27 SkinExtra = 8,
29 UiSkin = 9,
31 TaurenMane = 10,
33 Monster1 = 11,
35 Monster2 = 12,
37 Monster3 = 13,
39 ItemIcon = 14,
41 Unknown = 255,
43}
44
45impl M2TextureType {
46 pub fn from_u32(value: u32) -> Option<Self> {
48 match value {
49 0 => Some(Self::Hardcoded),
50 1 => Some(Self::Body),
51 2 => Some(Self::Item),
52 3 => Some(Self::WeaponArmorBasic),
53 4 => Some(Self::WeaponBlade),
54 5 => Some(Self::WeaponHandle),
55 6 => Some(Self::Environment),
56 7 => Some(Self::Hair),
57 8 => Some(Self::SkinExtra),
58 9 => Some(Self::UiSkin),
59 10 => Some(Self::TaurenMane),
60 11 => Some(Self::Monster1),
61 12 => Some(Self::Monster2),
62 13 => Some(Self::Monster3),
63 14 => Some(Self::ItemIcon),
64 _ => None,
65 }
66 }
67}
68
69bitflags::bitflags! {
70 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
72 pub struct M2TextureFlags: u32 {
73 const WRAP_X = 0x01;
75 const WRAP_Y = 0x02;
77 const NOT_REPLACEABLE = 0x04;
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct M2Texture {
86 pub texture_type: M2TextureType,
88 pub flags: M2TextureFlags,
90 pub filename: M2ArrayString,
92}
93
94impl M2Texture {
95 pub fn parse<R: Read + Seek>(reader: &mut R, _version: u32) -> Result<Self> {
97 let texture_type_raw = reader.read_u32_le()?;
98 let texture_type =
99 M2TextureType::from_u32(texture_type_raw).unwrap_or(M2TextureType::Unknown);
100
101 let flags = M2TextureFlags::from_bits_retain(reader.read_u32_le()?);
102 let filename = M2ArrayString::parse(reader)?;
103
104 Ok(Self {
105 texture_type,
106 flags,
107 filename,
108 })
109 }
110
111 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
113 writer.write_u32_le(self.texture_type as u32)?;
114 writer.write_u32_le(self.flags.bits())?;
115 self.filename.write(writer)?;
116
117 Ok(())
118 }
119
120 pub fn convert(&self, _target_version: M2Version) -> Self {
122 self.clone()
123 }
124
125 pub fn new(texture_type: M2TextureType, filename: M2ArrayString) -> Self {
127 Self {
128 texture_type,
129 flags: M2TextureFlags::empty(),
130 filename,
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use crate::common::{FixedString, M2Array};
138
139 use super::*;
140 use std::io::{Cursor, SeekFrom};
141
142 #[test]
143 fn test_texture_parse() {
144 let mut data = Vec::new();
145
146 let dummy = [0, 0, 0];
147 data.extend_from_slice(&dummy);
148
149 let filename_str = "test\0";
150 data.extend_from_slice(filename_str.as_bytes());
151
152 data.extend_from_slice(&1u32.to_le_bytes());
154
155 data.extend_from_slice(&3u32.to_le_bytes());
157
158 data.extend_from_slice(&(filename_str.len() as u32).to_le_bytes());
159 data.extend_from_slice(&(dummy.len() as u32).to_le_bytes());
160
161 let mut cursor = Cursor::new(data);
162 cursor
163 .seek(SeekFrom::Start((filename_str.len() + dummy.len()) as u64))
164 .unwrap();
165 let texture =
166 M2Texture::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
167
168 assert_eq!(texture.texture_type, M2TextureType::Body);
169 assert_eq!(
170 texture.flags,
171 M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y
172 );
173 assert_eq!(texture.filename.array.count, 5);
174 assert_eq!(texture.filename.array.offset, 3);
175 }
176
177 #[test]
178 fn test_texture_write() {
179 let texture = M2Texture {
180 texture_type: M2TextureType::Body,
181 flags: M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y,
182 filename: M2ArrayString {
183 string: FixedString { data: Vec::new() },
184 array: M2Array::new(10, 0x100),
185 },
186 };
187
188 let mut data = Vec::new();
189 texture.write(&mut data).unwrap();
190
191 assert_eq!(
192 data,
193 [
194 1, 0, 0, 0, 3, 0, 0, 0, 10, 0, 0, 0, 0, 1, 0, 0, ]
200 );
201 }
202
203 #[test]
204 fn test_texture_conversion() {
205 let texture = M2Texture {
206 texture_type: M2TextureType::Body,
207 flags: M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y,
208 filename: M2ArrayString {
209 string: FixedString { data: Vec::new() },
210 array: M2Array::new(10, 0x100),
211 },
212 };
213
214 let converted = texture.convert(M2Version::Cataclysm);
216
217 assert_eq!(converted.texture_type, texture.texture_type);
218 assert_eq!(converted.flags, texture.flags);
219 assert_eq!(converted.filename.array.count, texture.filename.array.count);
220 assert_eq!(
221 converted.filename.array.offset,
222 texture.filename.array.offset
223 );
224 }
225}