use crate::error::Error;
use crate::strings::{NsisString, StringSegment};
const PARK_CODE_SKIP: u16 = 0xE000;
const PARK_CODE_VAR: u16 = 0xE001;
const PARK_CODE_SHELL: u16 = 0xE002;
const PARK_CODE_LANG: u16 = 0xE003;
pub fn is_park_special(ch: u16) -> bool {
(PARK_CODE_SKIP..=PARK_CODE_LANG).contains(&ch)
}
fn read_u16(table: &[u8], offset: usize) -> Option<u16> {
table
.get(offset..)
.and_then(|s| s.first_chunk::<2>())
.copied()
.map(u16::from_le_bytes)
}
pub fn read_park_string(table: &[u8], offset: usize) -> Result<NsisString, Error> {
let mut segments = Vec::new();
let mut literal_chars: Vec<u16> = Vec::new();
let mut pos = offset;
while let Some(ch) = read_u16(table, pos) {
if ch == 0 {
break;
}
if is_park_special(ch) {
let Some(arg_pos) = pos.checked_add(2) else {
break;
};
let Some(n) = read_u16(table, arg_pos) else {
break;
};
pos = pos.saturating_add(4);
if n == 0 {
break;
}
if ch == PARK_CODE_SKIP {
literal_chars.push(n);
continue;
}
if !literal_chars.is_empty() {
let s = String::from_utf16_lossy(&literal_chars);
segments.push(StringSegment::Literal(s));
literal_chars.clear();
}
match ch {
PARK_CODE_VAR => {
let index = n & 0x7FFF;
segments.push(StringSegment::Variable(index));
}
PARK_CODE_SHELL => {
let folder = n & 0xFF;
segments.push(StringSegment::ShellFolder(folder));
}
PARK_CODE_LANG => {
let index = n & 0x7FFF;
segments.push(StringSegment::LangString(index));
}
_ => {}
}
} else {
literal_chars.push(ch);
pos = pos.saturating_add(2);
}
}
if !literal_chars.is_empty() {
let s = String::from_utf16_lossy(&literal_chars);
segments.push(StringSegment::Literal(s));
}
Ok(NsisString { segments })
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_u16s(units: &[u16]) -> Vec<u8> {
let mut buf = Vec::new();
for &u in units {
buf.extend_from_slice(&u.to_le_bytes());
}
buf
}
#[test]
fn plain_ascii_string() {
let table = encode_u16s(&[0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0000]);
let s = read_park_string(&table, 0).unwrap();
assert_eq!(s.segments.len(), 1);
assert_eq!(s.segments[0], StringSegment::Literal("Hello".into()));
}
#[test]
fn string_with_variable() {
let table = encode_u16s(&[
PARK_CODE_VAR,
21, 0x5C,
0x41,
0x70,
0x70, 0x0000,
]);
let s = read_park_string(&table, 0).unwrap();
assert_eq!(s.segments.len(), 2);
assert_eq!(s.segments[0], StringSegment::Variable(21));
assert_eq!(s.segments[1], StringSegment::Literal("\\App".into()));
}
#[test]
fn string_with_shell_folder() {
let table = encode_u16s(&[PARK_CODE_SHELL, 0x001A, 0x0000]);
let s = read_park_string(&table, 0).unwrap();
assert_eq!(s.segments.len(), 1);
assert_eq!(s.segments[0], StringSegment::ShellFolder(0x1A));
}
#[test]
fn skip_code() {
let table = encode_u16s(&[PARK_CODE_SKIP, PARK_CODE_VAR, 0x0000]);
let s = read_park_string(&table, 0).unwrap();
assert_eq!(s.segments.len(), 1);
let lit = &s.segments[0];
assert!(matches!(lit, StringSegment::Literal(_)));
}
#[test]
fn empty_string() {
let table = encode_u16s(&[0x0000]);
let s = read_park_string(&table, 0).unwrap();
assert!(s.is_empty());
}
#[test]
fn non_ascii_literal() {
let table = encode_u16s(&[0x00E4, 0x0000]);
let s = read_park_string(&table, 0).unwrap();
assert_eq!(s.segments.len(), 1);
assert_eq!(s.segments[0], StringSegment::Literal("ä".into()));
}
}