image_blp/parser/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
mod direct;
pub mod error;
mod header;
mod jpeg;
pub mod types;

#[cfg(test)]
mod tests;

use super::types::*;
use crate::path::make_mipmap_path;
use direct::parse_direct_content;
pub use error::{Error, LoadError};
use header::parse_header;
use jpeg::parse_jpeg_content;
use nom::error::context;
use std::path::{Path, PathBuf};
use types::Parser;

/// Read BLP file from file system. If it BLP0 format, uses the mipmaps near the root file.
pub fn load_blp<Q>(path: Q) -> Result<BlpImage, LoadError>
where
    Q: AsRef<Path>,
{
    let input =
        std::fs::read(&path).map_err(|e| LoadError::FileSystem(path.as_ref().to_owned(), e))?;
    load_blp_ex(Some(path), &input)
}

/// Read BLP file from buffer(Vec<u8>). If it BLP0 format, uses the mipmaps in the temp dir.
///
/// Since: 1.2.0
pub fn load_blp_from_buf(buf: &[u8]) -> Result<BlpImage, LoadError> {
    let input = buf;
    let path: Option<PathBuf> = None;
    load_blp_ex(path, input)
}

fn load_blp_ex<Q>(path: Option<Q>, input: &[u8]) -> Result<BlpImage, LoadError>
where
    Q: AsRef<Path>,
{
    // We have to preload all mipmaps in memory as we are constrained with Nom 'a lifetime that
    // should be equal of lifetime of root input stream.
    let mut mipmaps = vec![];
    if let Some(path) = path.as_ref() {
        for i in 0..16 {
            let mipmap_path = make_mipmap_path(path, i)
                .ok_or_else(|| LoadError::InvalidFilename(path.as_ref().to_owned()))?;
            if mipmap_path.is_file() {
                let mipmap = std::fs::read(mipmap_path)
                    .map_err(|e| LoadError::FileSystem(path.as_ref().to_owned(), e))?;
                mipmaps.push(mipmap);
            } else {
                break;
            }
        }
    }

    let image = match parse_blp_with_externals(input, |i| preloaded_mipmaps(&mipmaps, i)) {
        Ok((_, image)) => Ok(image),
        Err(nom::Err::Incomplete(needed)) => Err(LoadError::Incomplete(needed)),
        Err(nom::Err::Error(e)) => Err(LoadError::Parsing(format!("{}", e))),
        Err(nom::Err::Failure(e)) => Err(LoadError::Parsing(format!("{}", e))),
    }?;
    Ok(image)
}

/// Parse BLP file from slice and fail if we require parse external files (case BLP0)
pub fn parse_blp(input: &[u8]) -> Parser<BlpImage> {
    parse_blp_with_externals(input, no_mipmaps)
}

/// Helper for `parse_blp` when no external mipmaps are needed
pub fn no_mipmaps<'a>(_: usize) -> Result<Option<&'a [u8]>, Box<dyn std::error::Error>> {
    Ok(None)
}

/// Helper for `parse_blp` when external mipmaps are located in filesystem near the
/// root file and loaded in memory when reading the main file.
pub fn preloaded_mipmaps(
    mipmaps: &[Vec<u8>],
    i: usize,
) -> Result<Option<&[u8]>, Box<dyn std::error::Error>> {
    if i >= mipmaps.len() {
        Ok(None)
    } else {
        Ok(Some(&mipmaps[i]))
    }
}

/// Parse BLP file from slice and use user provided callback to read mipmaps
pub fn parse_blp_with_externals<'a, F>(
    root_input: &'a [u8],
    external_mipmaps: F,
) -> Parser<'a, BlpImage>
where
    F: FnMut(usize) -> Result<Option<&'a [u8]>, Box<dyn std::error::Error>> + Clone,
{
    // Parse header
    let (input, header) = context("header", parse_header)(root_input)?;

    // Parse image content
    let (input, content) = context("image content", |input| {
        parse_content(&header, external_mipmaps.clone(), root_input, input)
    })(input)?;

    Ok((input, BlpImage { header, content }))
}

fn parse_content<'a, F>(
    blp_header: &BlpHeader,
    external_mipmaps: F,
    original_input: &'a [u8],
    input: &'a [u8],
) -> Parser<'a, BlpContent>
where
    F: FnMut(usize) -> Result<Option<&'a [u8]>, Box<dyn std::error::Error>> + Clone,
{
    match blp_header.content {
        BlpContentTag::Jpeg => {
            let (input, content) = context("jpeg content", |input| {
                parse_jpeg_content(blp_header, external_mipmaps.clone(), original_input, input)
            })(input)?;
            Ok((input, BlpContent::Jpeg(content)))
        }
        BlpContentTag::Direct => {
            let (input, content) = context("direct content", |input| {
                parse_direct_content(blp_header, external_mipmaps.clone(), original_input, input)
            })(input)?;
            Ok((input, content))
        }
    }
}