1use std::{borrow::Cow, io::SeekFrom};
27
28use binrw::{binrw, BinRead, BinWrite};
29use image_dds::{ddsfile::Dds, Surface};
30use tegra_swizzle::surface::BlockDim;
31use xc3_write::Xc3Write;
32
33pub use tegra_swizzle::SwizzleError;
34
35use crate::{error::CreateMiblError, xc3_write_binwrite_impl};
36
37#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
39#[derive(Debug, PartialEq, Eq, Clone)]
40pub struct Mibl {
41 pub image_data: Vec<u8>,
45 pub footer: MiblFooter,
47}
48
49const MIBL_FOOTER_SIZE: u64 = 40;
50
51#[binrw]
53#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
54#[derive(Debug, PartialEq, Eq, Clone)]
55pub struct MiblFooter {
56 pub image_size: u32,
61 pub unk: u32, pub width: u32,
64 pub height: u32,
66 pub depth: u32,
68 pub view_dimension: ViewDimension,
69 pub image_format: ImageFormat,
70 pub mipmap_count: u32,
72 pub version: u32, #[brw(magic(b"LBIM"))]
75 #[br(temp)]
76 #[bw(ignore)]
77 _magic: (),
78}
79
80#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
81#[derive(BinRead, BinWrite, Debug, Clone, Copy, PartialEq, Eq)]
82#[brw(repr(u32))]
83pub enum ViewDimension {
84 D2 = 1,
85 D3 = 2,
86 Cube = 8,
87}
88
89#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
91#[derive(BinRead, BinWrite, Debug, Clone, Copy, PartialEq, Eq)]
92#[brw(repr(u32))]
93pub enum ImageFormat {
94 R8Unorm = 1,
95 R8G8B8A8Unorm = 37,
96 R16G16B16A16Float = 41,
97 R4G4B4A4Unorm = 57,
98 BC1Unorm = 66,
99 BC2Unorm = 67,
100 BC3Unorm = 68,
101 BC4Unorm = 73,
102 BC5Unorm = 75,
103 BC7Unorm = 77,
104 BC6UFloat = 80,
105 B8G8R8A8Unorm = 109,
106}
107
108impl ImageFormat {
109 pub fn block_dim(&self) -> BlockDim {
110 match self {
111 ImageFormat::R8Unorm => BlockDim::uncompressed(),
112 ImageFormat::R8G8B8A8Unorm => BlockDim::uncompressed(),
113 ImageFormat::R16G16B16A16Float => BlockDim::uncompressed(),
114 ImageFormat::R4G4B4A4Unorm => BlockDim::uncompressed(),
115 ImageFormat::BC1Unorm => BlockDim::block_4x4(),
116 ImageFormat::BC2Unorm => BlockDim::block_4x4(),
117 ImageFormat::BC3Unorm => BlockDim::block_4x4(),
118 ImageFormat::BC4Unorm => BlockDim::block_4x4(),
119 ImageFormat::BC5Unorm => BlockDim::block_4x4(),
120 ImageFormat::BC7Unorm => BlockDim::block_4x4(),
121 ImageFormat::BC6UFloat => BlockDim::block_4x4(),
122 ImageFormat::B8G8R8A8Unorm => BlockDim::uncompressed(),
123 }
124 }
125
126 pub fn bytes_per_pixel(&self) -> u32 {
127 match self {
128 ImageFormat::R8Unorm => 1,
129 ImageFormat::R8G8B8A8Unorm => 4,
130 ImageFormat::R16G16B16A16Float => 8,
131 ImageFormat::R4G4B4A4Unorm => 2,
132 ImageFormat::BC1Unorm => 8,
133 ImageFormat::BC2Unorm => 16,
134 ImageFormat::BC3Unorm => 16,
135 ImageFormat::BC4Unorm => 8,
136 ImageFormat::BC5Unorm => 16,
137 ImageFormat::BC7Unorm => 16,
138 ImageFormat::BC6UFloat => 16,
139 ImageFormat::B8G8R8A8Unorm => 4,
140 }
141 }
142}
143
144impl BinRead for Mibl {
145 type Args<'a> = ();
146
147 fn read_options<R: std::io::Read + std::io::Seek>(
148 reader: &mut R,
149 endian: binrw::Endian,
150 args: Self::Args<'_>,
151 ) -> binrw::BinResult<Self> {
152 let saved_pos = reader.stream_position()?;
153
154 reader.seek(SeekFrom::End(-(MIBL_FOOTER_SIZE as i64)))?;
156 let footer = MiblFooter::read_options(reader, endian, args)?;
157
158 reader.seek(SeekFrom::Start(saved_pos))?;
159
160 let unaligned_size = footer.swizzled_surface_size();
163 let mut image_data = vec![0u8; unaligned_size];
164 reader.read_exact(&mut image_data)?;
165
166 Ok(Mibl { image_data, footer })
167 }
168}
169
170impl BinWrite for Mibl {
171 type Args<'a> = ();
172
173 fn write_options<W: std::io::Write + std::io::Seek>(
174 &self,
175 writer: &mut W,
176 endian: binrw::Endian,
177 _args: Self::Args<'_>,
178 ) -> binrw::BinResult<()> {
179 let unaligned_size = self.image_data.len() as u64;
181 let aligned_size = unaligned_size.next_multiple_of(4096);
182
183 self.image_data.write_options(writer, endian, ())?;
184
185 let padding_size = aligned_size - unaligned_size;
188 writer.write_all(&vec![0u8; padding_size as usize])?;
189
190 if padding_size < MIBL_FOOTER_SIZE {
191 writer.write_all(&[0u8; 4096])?;
192 }
193
194 writer.seek(SeekFrom::End(-(MIBL_FOOTER_SIZE as i64)))?;
195 self.footer.write_options(writer, endian, ())?;
196
197 Ok(())
198 }
199}
200
201xc3_write_binwrite_impl!(Mibl);
202
203impl Mibl {
204 pub fn deswizzled_image_data(&self) -> Result<Vec<u8>, SwizzleError> {
206 if self
207 .footer
208 .width
209 .checked_mul(self.footer.height)
210 .and_then(|i| i.checked_mul(self.footer.depth))
211 .is_none()
212 {
213 return Err(SwizzleError::NotEnoughData {
214 expected_size: usize::MAX,
215 actual_size: self.image_data.len(),
216 });
217 }
218 tegra_swizzle::surface::deswizzle_surface(
219 self.footer.width,
220 self.footer.height,
221 self.footer.depth,
222 &self.image_data,
223 self.footer.image_format.block_dim(),
224 None,
225 self.footer.image_format.bytes_per_pixel(),
226 self.footer.mipmap_count,
227 if self.footer.view_dimension == ViewDimension::Cube {
228 6
229 } else {
230 1
231 },
232 )
233 }
234
235 pub fn to_surface_with_base_mip(
238 &self,
239 base_mip_level: &[u8],
240 ) -> Result<Surface<Vec<u8>>, SwizzleError> {
241 let mid = self.to_surface()?;
242
243 let width = mid.width.saturating_mul(2);
244 let height = mid.height.saturating_mul(2);
245
246 let mut data = tegra_swizzle::surface::deswizzle_surface(
250 width,
251 height,
252 self.footer.depth,
253 base_mip_level,
254 self.footer.image_format.block_dim(),
255 None,
256 self.footer.image_format.bytes_per_pixel(),
257 1,
258 if self.footer.view_dimension == ViewDimension::Cube {
259 6
260 } else {
261 1
262 },
263 )?;
264
265 if self.footer.image_format == ImageFormat::R4G4B4A4Unorm {
266 swap_red_blue_unorm4(&mut data);
268 }
269
270 data.extend_from_slice(&mid.data);
271
272 Ok(Surface {
275 width,
276 height,
277 depth: mid.depth,
278 layers: mid.layers,
279 mipmaps: mid.mipmaps.saturating_add(1),
280 image_format: mid.image_format,
281 data,
282 })
283 }
284
285 pub fn split_base_mip(&self) -> (Self, Vec<u8>) {
288 let base_mip_size = self.footer.swizzled_base_mip_size();
290 let (base_mip, image_data) = self.image_data.split_at(base_mip_size);
291
292 (
293 Self {
294 image_data: image_data.to_vec(),
295 footer: MiblFooter {
296 image_size: image_data.len().next_multiple_of(4096) as u32,
297 width: self.footer.width / 2,
298 height: self.footer.height / 2,
299 mipmap_count: self.footer.mipmap_count - 1,
300 ..self.footer
301 },
302 },
303 base_mip.to_vec(),
304 )
305 }
306
307 pub fn to_surface(&self) -> Result<Surface<Vec<u8>>, SwizzleError> {
309 let mut data = self.deswizzled_image_data()?;
310 if self.footer.image_format == ImageFormat::R4G4B4A4Unorm {
311 swap_red_blue_unorm4(&mut data);
313 }
314 Ok(Surface {
315 width: self.footer.width,
316 height: self.footer.height,
317 depth: self.footer.depth,
318 layers: if self.footer.view_dimension == ViewDimension::Cube {
319 6
320 } else {
321 1
322 },
323 mipmaps: self.footer.mipmap_count,
324 image_format: self.footer.image_format.into(),
325 data,
326 })
327 }
328
329 pub fn from_surface<T: AsRef<[u8]>>(surface: Surface<T>) -> Result<Self, CreateMiblError> {
333 let Surface {
334 width,
335 height,
336 depth,
337 layers,
338 mipmaps,
339 image_format,
340 data,
341 } = surface;
342 let image_format = ImageFormat::try_from(image_format)?;
343
344 let data = if image_format == ImageFormat::R4G4B4A4Unorm {
345 let mut data = data.as_ref().to_vec();
347 swap_red_blue_unorm4(&mut data);
348 Cow::Owned(data)
349 } else {
350 Cow::Borrowed(data.as_ref())
351 };
352
353 let image_data = tegra_swizzle::surface::swizzle_surface(
354 width,
355 height,
356 depth,
357 &data,
358 image_format.block_dim(),
359 None,
360 image_format.bytes_per_pixel(),
361 mipmaps,
362 layers,
363 )?;
364
365 let image_size = image_data.len().next_multiple_of(4096) as u32;
366
367 Ok(Self {
368 image_data,
369 footer: MiblFooter {
370 image_size,
371 unk: 4096,
372 width,
373 height,
374 depth,
375 view_dimension: if depth > 1 {
376 ViewDimension::D3
377 } else if layers == 6 {
378 ViewDimension::Cube
379 } else {
380 ViewDimension::D2
381 },
382 image_format,
383 mipmap_count: mipmaps,
384 version: 10001,
385 },
386 })
387 }
388
389 pub fn to_dds(&self) -> Result<Dds, crate::dds::CreateDdsError> {
391 self.to_surface()?.to_dds().map_err(Into::into)
392 }
393
394 pub fn from_dds(dds: &Dds) -> Result<Self, CreateMiblError> {
398 let surface = image_dds::Surface::from_dds(dds)?;
399 Self::from_surface(surface)
400 }
401}
402
403impl MiblFooter {
404 fn swizzled_surface_size(&self) -> usize {
405 tegra_swizzle::surface::swizzled_surface_size(
406 self.width,
407 self.height,
408 self.depth,
409 self.image_format.block_dim(),
410 None,
411 self.image_format.bytes_per_pixel(),
412 self.mipmap_count,
413 if self.view_dimension == ViewDimension::Cube {
414 6
415 } else {
416 1
417 },
418 )
419 }
420
421 pub(crate) fn deswizzled_surface_size(&self) -> usize {
422 tegra_swizzle::surface::deswizzled_surface_size(
423 self.width,
424 self.height,
425 self.depth,
426 self.image_format.block_dim(),
427 self.image_format.bytes_per_pixel(),
428 self.mipmap_count,
429 if self.view_dimension == ViewDimension::Cube {
430 6
431 } else {
432 1
433 },
434 )
435 }
436
437 fn swizzled_base_mip_size(&self) -> usize {
438 tegra_swizzle::surface::swizzled_surface_size(
439 self.width,
440 self.height,
441 self.depth,
442 self.image_format.block_dim(),
443 None,
444 self.image_format.bytes_per_pixel(),
445 1,
446 if self.view_dimension == ViewDimension::Cube {
447 6
448 } else {
449 1
450 },
451 )
452 }
453}
454
455impl From<ImageFormat> for image_dds::ImageFormat {
456 fn from(value: ImageFormat) -> Self {
457 match value {
458 ImageFormat::R8Unorm => image_dds::ImageFormat::R8Unorm,
459 ImageFormat::R8G8B8A8Unorm => image_dds::ImageFormat::Rgba8Unorm,
460 ImageFormat::R16G16B16A16Float => image_dds::ImageFormat::Rgba16Float,
461 ImageFormat::R4G4B4A4Unorm => image_dds::ImageFormat::Bgra4Unorm, ImageFormat::BC1Unorm => image_dds::ImageFormat::BC1RgbaUnorm,
463 ImageFormat::BC2Unorm => image_dds::ImageFormat::BC2RgbaUnorm,
464 ImageFormat::BC3Unorm => image_dds::ImageFormat::BC3RgbaUnorm,
465 ImageFormat::BC4Unorm => image_dds::ImageFormat::BC4RUnorm,
466 ImageFormat::BC5Unorm => image_dds::ImageFormat::BC5RgUnorm,
467 ImageFormat::BC7Unorm => image_dds::ImageFormat::BC7RgbaUnorm,
468 ImageFormat::BC6UFloat => image_dds::ImageFormat::BC6hRgbUfloat,
469 ImageFormat::B8G8R8A8Unorm => image_dds::ImageFormat::Bgra8Unorm,
470 }
471 }
472}
473
474impl TryFrom<image_dds::ImageFormat> for ImageFormat {
475 type Error = CreateMiblError;
476
477 fn try_from(value: image_dds::ImageFormat) -> Result<Self, Self::Error> {
478 match value {
479 image_dds::ImageFormat::R8Unorm => Ok(ImageFormat::R8Unorm),
480 image_dds::ImageFormat::Rgba8Unorm => Ok(ImageFormat::R8G8B8A8Unorm),
481 image_dds::ImageFormat::Rgba16Float => Ok(ImageFormat::R16G16B16A16Float),
482 image_dds::ImageFormat::Bgra8Unorm => Ok(ImageFormat::B8G8R8A8Unorm),
483 image_dds::ImageFormat::BC1RgbaUnorm => Ok(ImageFormat::BC1Unorm),
484 image_dds::ImageFormat::BC2RgbaUnorm => Ok(ImageFormat::BC2Unorm),
485 image_dds::ImageFormat::BC3RgbaUnorm => Ok(ImageFormat::BC3Unorm),
486 image_dds::ImageFormat::BC4RUnorm => Ok(ImageFormat::BC4Unorm),
487 image_dds::ImageFormat::BC5RgUnorm => Ok(ImageFormat::BC5Unorm),
488 image_dds::ImageFormat::BC6hRgbUfloat => Ok(ImageFormat::BC6UFloat),
489 image_dds::ImageFormat::BC7RgbaUnorm => Ok(ImageFormat::BC7Unorm),
490 image_dds::ImageFormat::Bgra4Unorm => Ok(ImageFormat::R4G4B4A4Unorm),
491 _ => Err(CreateMiblError::UnsupportedImageFormat(value)),
492 }
493 }
494}
495
496fn swap_red_blue_unorm4(data: &mut [u8]) {
497 data.chunks_exact_mut(2).for_each(|c| {
498 let r = c[1] & 0xF;
500 let b = c[0] & 0xF;
501 c[0] = c[0] & 0xF0 | r;
502 c[1] = c[1] & 0xF0 | b;
503 });
504}