serde_hkx_features/convert/
hkx_checker.rs

1//! Check the HKX file header to detect its format.
2use std::path::{Path, PathBuf};
3
4use crate::Format;
5
6/// Check the HKX file header to detect its format(`Win32`/`Amd64`).
7///
8/// # Errors
9/// - If tag file magic number
10/// - If hkx magic number is invalid
11pub fn detect_hkx_format(input_path: &Path) -> Result<Format, Error> {
12    let header = {
13        let mut header = [0_u8; 17];
14        let mut file = std::fs::File::open(input_path).map_err(|source| Error::FailedReadFile {
15            source,
16            path: input_path.to_path_buf(),
17        })?;
18        std::io::Read::read_exact(&mut file, &mut header).map_err(|source| {
19            Error::FailedReadFile {
20                source,
21                path: input_path.to_path_buf(),
22            }
23        })?;
24        header
25    };
26
27    // Actually, both LE and SE versions of hkt can be loaded, and there are mods disguised as hkx files. Example: Ride Sharing's `rsh_horsepinion.hkx`
28    // This is the processing for that.
29    // NOTE: Tag files cannot be converted by serde_hkx, so they are skipped.
30    let is_tag_file = {
31        /// Tag file(.hkt) magic bytes
32        const EXPECTED_MAGIC: [u8; 8] = [
33            0x1E, 0x0D, 0xB0, 0xCA, // magic0
34            0xCE, 0xFA, 0x11, 0xD0, // magic1
35        ];
36        header[0..8] == EXPECTED_MAGIC
37    };
38    if is_tag_file {
39        return Err(Error::UnsupportedTagFile {
40            path: input_path.to_path_buf(),
41        });
42    }
43
44    let is_hkx = {
45        /// .hkx magic bytes
46        const EXPECTED_MAGIC: [u8; 8] = [
47            0x57, 0xE0, 0xE0, 0x57, // magic0
48            0x10, 0xC0, 0xC0, 0x10, // magic1
49        ];
50        header[0..8] == EXPECTED_MAGIC
51    };
52    if !is_hkx {
53        return Err(Error::HkxInvalidMagic {
54            input_path: input_path.to_path_buf(),
55            magic_bytes: header,
56        });
57    }
58
59    // check ptr size
60    let ptr_size = header[16];
61    let current_format = match ptr_size {
62        4 => Format::Win32,
63        8 => Format::Amd64,
64        _ => {
65            return Err(Error::HkxInvalidHeader {
66                input_path: input_path.to_path_buf(),
67                actual: ptr_size,
68            });
69        }
70    };
71
72    Ok(current_format)
73}
74
75#[allow(clippy::enum_variant_names)]
76#[derive(Debug, snafu::Snafu)]
77#[snafu(visibility(pub))]
78pub enum Error {
79    #[snafu(display("serde-hkx does not currently support tag files. path: {}", path.display()))]
80    UnsupportedTagFile { path: PathBuf },
81
82    /// Failed to read this path.
83    #[snafu(display("Failed to read this path: {}", path.display()))]
84    FailedReadFile {
85        source: std::io::Error,
86        path: PathBuf,
87    },
88
89    #[snafu(display(
90        "HKX file {} did not have the expected Havok magic numbers. \
91        Expected magic=[0x57, 0xe0, 0xe0, 0x57, 0x10, 0xc0, 0xc0, 0x10, ...], \
92        but got {magic_bytes:x?}. \
93        This file is not a valid Havok animation or may be from an unsupported version.",
94        input_path.display()
95    ))]
96    HkxInvalidMagic {
97        input_path: PathBuf,
98        magic_bytes: [u8; 17],
99    },
100
101    #[snafu(display(
102        "HKX pointer size check failed for {}. \
103        Expected pointer size 4/8-byte, but could not determine a valid header or got {actual}-byte. \
104        The HKX may be malformed or from an incompatible platform.",
105        input_path.display()
106    ))]
107    HkxInvalidHeader { input_path: PathBuf, actual: u8 },
108}