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 Accessories = 8,
29 Custom1 = 9,
31 Custom2 = 10,
33 Custom3 = 11,
35}
36
37impl M2TextureType {
38 pub fn from_u32(value: u32) -> Option<Self> {
40 match value {
41 0 => Some(Self::Hardcoded),
42 1 => Some(Self::Body),
43 2 => Some(Self::Item),
44 3 => Some(Self::WeaponArmorBasic),
45 4 => Some(Self::WeaponBlade),
46 5 => Some(Self::WeaponHandle),
47 6 => Some(Self::Environment),
48 7 => Some(Self::Hair),
49 8 => Some(Self::Accessories),
50 9 => Some(Self::Custom1),
51 10 => Some(Self::Custom2),
52 11 => Some(Self::Custom3),
53 _ => None,
54 }
55 }
56}
57
58bitflags::bitflags! {
59 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
61 pub struct M2TextureFlags: u32 {
62 const WRAP_X = 0x01;
64 const WRAP_Y = 0x02;
66 const NOT_REPLACEABLE = 0x04;
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct M2Texture {
75 pub texture_type: M2TextureType,
77 pub flags: M2TextureFlags,
79 pub filename: M2ArrayString,
81}
82
83impl M2Texture {
84 pub fn parse<R: Read + Seek>(reader: &mut R, _version: u32) -> Result<Self> {
86 let texture_type_raw = reader.read_u32_le()?;
87 let texture_type =
88 M2TextureType::from_u32(texture_type_raw).unwrap_or(M2TextureType::Hardcoded);
89
90 let flags = M2TextureFlags::from_bits_retain(reader.read_u32_le()?);
91 let filename = M2ArrayString::parse(reader)?;
92
93 Ok(Self {
94 texture_type,
95 flags,
96 filename,
97 })
98 }
99
100 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
102 writer.write_u32_le(self.texture_type as u32)?;
103 writer.write_u32_le(self.flags.bits())?;
104 self.filename.write(writer)?;
105
106 Ok(())
107 }
108
109 pub fn convert(&self, _target_version: M2Version) -> Self {
111 self.clone()
112 }
113
114 pub fn new(texture_type: M2TextureType, filename: M2ArrayString) -> Self {
116 Self {
117 texture_type,
118 flags: M2TextureFlags::empty(),
119 filename,
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use crate::common::{FixedString, M2Array};
127
128 use super::*;
129 use std::io::{Cursor, SeekFrom};
130
131 #[test]
132 fn test_texture_parse() {
133 let mut data = Vec::new();
134
135 let dummy = [0, 0, 0];
136 data.extend_from_slice(&dummy);
137
138 let filename_str = "test\0";
139 data.extend_from_slice(filename_str.as_bytes());
140
141 data.extend_from_slice(&1u32.to_le_bytes());
143
144 data.extend_from_slice(&3u32.to_le_bytes());
146
147 data.extend_from_slice(&(filename_str.len() as u32).to_le_bytes());
148 data.extend_from_slice(&(dummy.len() as u32).to_le_bytes());
149
150 let mut cursor = Cursor::new(data);
151 cursor
152 .seek(SeekFrom::Start((filename_str.len() + dummy.len()) as u64))
153 .unwrap();
154 let texture =
155 M2Texture::parse(&mut cursor, M2Version::Classic.to_header_version()).unwrap();
156
157 assert_eq!(texture.texture_type, M2TextureType::Body);
158 assert_eq!(
159 texture.flags,
160 M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y
161 );
162 assert_eq!(texture.filename.array.count, 5);
163 assert_eq!(texture.filename.array.offset, 3);
164 }
165
166 #[test]
167 fn test_texture_write() {
168 let texture = M2Texture {
169 texture_type: M2TextureType::Body,
170 flags: M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y,
171 filename: M2ArrayString {
172 string: FixedString { data: Vec::new() },
173 array: M2Array::new(10, 0x100),
174 },
175 };
176
177 let mut data = Vec::new();
178 texture.write(&mut data).unwrap();
179
180 assert_eq!(
181 data,
182 [
183 1, 0, 0, 0, 3, 0, 0, 0, 10, 0, 0, 0, 0, 1, 0, 0, ]
189 );
190 }
191
192 #[test]
193 fn test_texture_conversion() {
194 let texture = M2Texture {
195 texture_type: M2TextureType::Body,
196 flags: M2TextureFlags::WRAP_X | M2TextureFlags::WRAP_Y,
197 filename: M2ArrayString {
198 string: FixedString { data: Vec::new() },
199 array: M2Array::new(10, 0x100),
200 },
201 };
202
203 let converted = texture.convert(M2Version::Cataclysm);
205
206 assert_eq!(converted.texture_type, texture.texture_type);
207 assert_eq!(converted.flags, texture.flags);
208 assert_eq!(converted.filename.array.count, texture.filename.array.count);
209 assert_eq!(
210 converted.filename.array.offset,
211 texture.filename.array.offset
212 );
213 }
214}