ramensoftware/windhawk · error · SettingsError

odd-length or non-hex value

Error message

odd-length or non-hex value

What it means

get_binary decodes the stored string as hex; this error means the stored string was not valid hex (contains non-hex characters) or has an odd number of characters, so it cannot map to whole bytes. It indicates the value was written outside set_binary or corrupted in the INI file.

Solutions

  1. Check the INI file and fix the value to be an even-length hex string.
  2. Rewrite the value with set_binary so it is stored as proper hex.
  3. Wrap get_binary and treat this error as a missing/corrupt value: fall back to a default and re-store it.
  4. Verify the setting name and section point at the intended key rather than an unrelated string value.

Example fix

// before
let data = settings.get_binary("blob")?; // Err: odd-length or non-hex
// after
let data = match settings.get_binary("blob") {
    Ok(d) => d.unwrap_or_default(),
    Err(_) => { settings.set_binary("blob", &default_bytes)?; default_bytes.to_vec() }
};
Defensive patterns

Strategy: validation

Validate before calling

fn is_hex(s: &str) -> bool { s.len() % 2 == 0 && s.chars().all(|c| c.is_ascii_hexdigit()) }
// call is_hex(&stored) before get_binary

Type guard

fn looks_like_hex_blob(s: &str) -> bool { s.len() % 2 == 0 && s.bytes().all(|b| b.is_ascii_hexdigit()) }

Try / catch

let data = settings.get_binary("blob").or_else(|_| {
    settings.set_binary("blob", &[])?;
    Ok(None)
})?;

Prevention

When it happens

Trigger: Calling get_binary on a name whose stored INI value is an arbitrary string (set manually, by an older version, or via set_string) that is not an even-length hex string, e.g. "abc" (odd length) or "xyz" (non-hex).

Common situations: Hand-editing the mod's INI file and mistyping a binary value; a value written by a tool that does not hex-encode; schema migration where a string setting is now read as binary.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12). Data as JSON: /api/errors/646d5b5022509372. Report an issue: GitHub.

Appendix: source

Thrown at src/windhawk-core/windows/src/ini.rs:217

        write_profile(&self.file, &self.section, Some(name), Some(&escaped))
    }

    fn get_int(&self, name: &str) -> Result<Option<i32>, SettingsError> {
        Ok(self.get_string(name)?.map(|s| parse_c_int(&s)))
    }

    fn set_int(&mut self, name: &str, value: i32) -> Result<(), SettingsError> {
        // SetInt -> SetString(to_wstring(value)); a decimal triggers no
        // escaping, but route through set_string for exactness.
        self.set_string(name, &value.to_string())
    }

    fn get_binary(&self, name: &str) -> Result<Option<Vec<u8>>, SettingsError> {
        match self.get_string(name)? {
            None => Ok(None),
            Some(s) => decode_hex(&s)
                .map(Some)
                .ok_or_else(|| self.err("get_binary", 0, "odd-length or non-hex value")),
        }
    }

    fn set_binary(&mut self, name: &str, value: &[u8]) -> Result<(), SettingsError> {
        self.set_string(name, &encode_hex(value))
    }

    fn remove(&mut self, name: &str) -> Result<(), SettingsError> {
        self.check_name("remove", name)?;
        // WritePrivateProfileString(section, name, NULL, file) removes the
        // value.
        write_profile(&self.file, &self.section, Some(name), None)
    }

    fn enum_values(&self) -> Result<Vec<(String, TreeValue)>, SettingsError> {
        let names = enum_profile_names(&self.file, &self.section)
            .map_err(|os| self.err("enum", os, "GetPrivateProfileString"))?;
        let mut out = Vec::with_capacity(names.len());

View on GitHub (pinned to 61d99ed8e1)