use crate::{
error::Error,
util::{
encoding::{is_unicode_for_version, read_ansi_bytes},
read::Reader,
},
version::Version,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LanguageCodepage {
Utf16Le,
Windows(u32),
Other(u32),
}
impl LanguageCodepage {
#[must_use]
pub fn raw(self) -> u32 {
match self {
Self::Utf16Le => 1200,
Self::Windows(codepage) | Self::Other(codepage) => codepage,
}
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Utf16Le => "utf16le",
Self::Windows(_) => "windows",
Self::Other(_) => "other",
}
}
}
impl core::fmt::Display for LanguageCodepage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.label())
}
}
#[derive(Clone, Debug)]
pub struct LanguageEntry {
pub name: Vec<u8>,
pub language_name: Vec<u8>,
pub dialog_font: Vec<u8>,
pub title_font: Vec<u8>,
pub welcome_font: Vec<u8>,
pub copyright_font: Vec<u8>,
pub data: Vec<u8>,
pub license_text: Vec<u8>,
pub info_before: Vec<u8>,
pub info_after: Vec<u8>,
pub language_id: u32,
pub codepage: LanguageCodepage,
pub dialog_font_size: u32,
pub dialog_font_standard_height: u32,
pub title_font_size: u32,
pub welcome_font_size: u32,
pub copyright_font_size: u32,
pub right_to_left: bool,
}
impl LanguageEntry {
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
if version.at_least(6, 6, 0) {
return read_v6_6(reader, version);
}
read_legacy(reader, version)
}
#[must_use]
pub fn name_string(&self) -> Option<String> {
decode_with_codepage(&self.name, self.codepage)
}
#[must_use]
pub fn language_name_string(&self) -> Option<String> {
decode_with_codepage(&self.language_name, self.codepage)
}
}
fn read_v6_6(reader: &mut Reader<'_>, _version: &Version) -> Result<LanguageEntry, Error> {
let name = read_ansi_bytes(reader, "Language.Name")?;
let language_name = read_ansi_bytes(reader, "Language.LanguageName")?;
let dialog_font = read_ansi_bytes(reader, "Language.DialogFont")?;
let welcome_font = read_ansi_bytes(reader, "Language.WelcomeFont")?;
let data = read_ansi_bytes(reader, "Language.Data")?;
let license_text = read_ansi_bytes(reader, "Language.LicenseText")?;
let info_before = read_ansi_bytes(reader, "Language.InfoBefore")?;
let info_after = read_ansi_bytes(reader, "Language.InfoAfter")?;
let language_id_word = reader.u16_le("Language.LanguageId")?;
let language_id = u32::from(language_id_word);
let codepage = LanguageCodepage::Utf16Le;
let dialog_font_size = reader.u32_le("Language.DialogFontSize")?;
let dialog_font_standard_height = reader.u32_le("Language.DialogFontBaseScaleHeight")?;
let _dialog_font_base_scale_width = reader.u32_le("Language.DialogFontBaseScaleWidth")?;
let welcome_font_size = reader.u32_le("Language.WelcomeFontSize")?;
let right_to_left = reader.u8("Language.RightToLeft")? != 0;
Ok(LanguageEntry {
name,
language_name,
dialog_font,
title_font: Vec::new(),
welcome_font,
copyright_font: Vec::new(),
data,
license_text,
info_before,
info_after,
language_id,
codepage,
dialog_font_size,
dialog_font_standard_height,
title_font_size: 0,
welcome_font_size,
copyright_font_size: 0,
right_to_left,
})
}
fn read_legacy(reader: &mut Reader<'_>, version: &Version) -> Result<LanguageEntry, Error> {
let name = if version.at_least(4, 0, 0) {
read_ansi_bytes(reader, "Language.Name")?
} else {
Vec::new()
};
let language_name = read_ansi_bytes(reader, "Language.LanguageName")?;
if version.at_least_4(5, 5, 7, 1) && !version.at_least_4(5, 5, 7, 2) {
let _skip = read_ansi_bytes(reader, "Language.BlackBoxSkip")?;
}
let dialog_font = read_ansi_bytes(reader, "Language.DialogFont")?;
let title_font = read_ansi_bytes(reader, "Language.TitleFont")?;
let welcome_font = read_ansi_bytes(reader, "Language.WelcomeFont")?;
let copyright_font = read_ansi_bytes(reader, "Language.CopyrightFont")?;
let data = if version.at_least(4, 0, 0) {
read_ansi_bytes(reader, "Language.Data")?
} else {
Vec::new()
};
let (license_text, info_before, info_after) = if version.at_least(4, 0, 1) {
(
read_ansi_bytes(reader, "Language.LicenseText")?,
read_ansi_bytes(reader, "Language.InfoBefore")?,
read_ansi_bytes(reader, "Language.InfoAfter")?,
)
} else {
(Vec::new(), Vec::new(), Vec::new())
};
let language_id = reader.u32_le("Language.LanguageId")?;
let codepage = if version.at_least(4, 2, 2) {
if is_unicode_for_version(version) {
if !version.at_least(5, 3, 0) {
let _skip = reader.u32_le("Language.CodepageSkip")?;
}
LanguageCodepage::Utf16Le
} else {
let cp = reader.u32_le("Language.Codepage")?;
if cp == 0 {
LanguageCodepage::Windows(1252)
} else {
LanguageCodepage::Windows(cp)
}
}
} else if is_unicode_for_version(version) {
LanguageCodepage::Utf16Le
} else {
LanguageCodepage::Windows(1252)
};
let dialog_font_size = reader.u32_le("Language.DialogFontSize")?;
let dialog_font_standard_height = if !version.at_least(4, 1, 0) {
reader.u32_le("Language.DialogFontStandardHeight")?
} else {
0
};
let title_font_size = reader.u32_le("Language.TitleFontSize")?;
let welcome_font_size = reader.u32_le("Language.WelcomeFontSize")?;
let copyright_font_size = reader.u32_le("Language.CopyrightFontSize")?;
if version.at_least_4(5, 5, 7, 1) && !version.at_least_4(5, 5, 7, 2) {
let _ = reader.u32_le("Language.BlackBoxTail")?;
}
let right_to_left = if version.at_least(5, 2, 3) {
reader.u8("Language.RightToLeft")? != 0
} else {
false
};
Ok(LanguageEntry {
name,
language_name,
dialog_font,
title_font,
welcome_font,
copyright_font,
data,
license_text,
info_before,
info_after,
language_id,
codepage,
dialog_font_size,
dialog_font_standard_height,
title_font_size,
welcome_font_size,
copyright_font_size,
right_to_left,
})
}
fn decode_with_codepage(bytes: &[u8], codepage: LanguageCodepage) -> Option<String> {
match codepage {
LanguageCodepage::Utf16Le => decode_utf16le(bytes),
LanguageCodepage::Windows(_) | LanguageCodepage::Other(_) => {
std::str::from_utf8(bytes)
.map(str::to_owned)
.ok()
.or_else(|| Some(String::from_utf8_lossy(bytes).into_owned()))
}
}
}
fn decode_utf16le(bytes: &[u8]) -> Option<String> {
if !bytes.len().is_multiple_of(2) {
return None;
}
let mut units = Vec::with_capacity(bytes.len() / 2);
for chunk in bytes.chunks_exact(2) {
let mut arr = [0u8; 2];
arr.copy_from_slice(chunk);
units.push(u16::from_le_bytes(arr));
}
String::from_utf16(&units).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::version::{Version, VersionFlags};
fn v6_4_unicode() -> Version {
Version {
a: 6,
b: 4,
c: 0,
d: 0,
flags: VersionFlags::UNICODE,
raw_marker: [0u8; 64],
}
}
fn put_blob(buf: &mut Vec<u8>, b: &[u8]) {
let len = u32::try_from(b.len()).unwrap();
buf.extend_from_slice(&len.to_le_bytes());
buf.extend_from_slice(b);
}
fn put_utf16(buf: &mut Vec<u8>, s: &str) {
let bytes: Vec<u8> = s.encode_utf16().flat_map(u16::to_le_bytes).collect();
put_blob(buf, &bytes);
}
#[test]
fn parses_minimal_english_language() {
let v = v6_4_unicode();
let mut bytes = Vec::new();
put_utf16(&mut bytes, "english"); put_utf16(&mut bytes, "English"); put_utf16(&mut bytes, "Tahoma"); put_utf16(&mut bytes, "Verdana"); put_utf16(&mut bytes, "Verdana"); put_utf16(&mut bytes, "Arial"); put_blob(&mut bytes, &[0xDE, 0xAD]); put_utf16(&mut bytes, ""); put_utf16(&mut bytes, ""); put_utf16(&mut bytes, ""); bytes.extend_from_slice(&0x0409u32.to_le_bytes()); bytes.extend_from_slice(&8u32.to_le_bytes()); bytes.extend_from_slice(&12u32.to_le_bytes()); bytes.extend_from_slice(&12u32.to_le_bytes()); bytes.extend_from_slice(&9u32.to_le_bytes()); bytes.push(0);
let mut r = Reader::new(&bytes);
let l = LanguageEntry::read(&mut r, &v).unwrap();
assert_eq!(l.language_id, 0x0409);
assert_eq!(l.codepage, LanguageCodepage::Utf16Le);
assert_eq!(l.dialog_font_size, 8);
assert!(!l.right_to_left);
assert_eq!(l.name_string().as_deref(), Some("english"));
assert_eq!(l.language_name_string().as_deref(), Some("English"));
assert_eq!(l.data, vec![0xDE, 0xAD]);
assert_eq!(r.pos(), bytes.len());
}
}