Skip to content

Commit 1a14144

Browse files
committed
Resolve NSIS language strings
1 parent 8b39f94 commit 1a14144

File tree

9 files changed

+251
-305
lines changed

9 files changed

+251
-305
lines changed

src/installers/nsis/entry/mod.rs

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use crate::installers::nsis::strings::encoding::nsis_string;
2-
use crate::installers::nsis::version::NsisVersion;
1+
use crate::installers::nsis::state::NsisState;
32
use crate::installers::utils::registry::RegRoot;
43
use std::borrow::Cow;
54
use std::ops::Not;
@@ -313,50 +312,43 @@ pub enum Entry {
313312

314313
impl Entry {
315314
#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
316-
pub fn update_vars<'str_block>(
317-
&self,
318-
strings_block: &'str_block [u8],
319-
user_vars: &mut [Cow<'str_block, str>; 9],
320-
nsis_version: NsisVersion,
321-
) {
315+
pub fn update_vars(&self, state: &mut NsisState) {
322316
match self {
323317
Self::GetFullPathname { output, .. } => {
324-
user_vars[1] = nsis_string(strings_block, output.get(), user_vars, nsis_version);
318+
state.user_variables[1] = state.get_string(output.get());
325319
}
326320
Self::SearchPath { filename, .. } => {
327-
user_vars[0] = nsis_string(strings_block, filename.get(), user_vars, nsis_version);
321+
state.user_variables[0] = state.get_string(filename.get());
328322
}
329323
Self::GetTempFilename { base_dir, .. } => {
330-
user_vars[0] = nsis_string(strings_block, base_dir.get(), user_vars, nsis_version);
324+
state.user_variables[0] = state.get_string(base_dir.get());
331325
}
332326
Self::ExtractFile { name, .. } => {
333-
user_vars[0] = nsis_string(strings_block, name.get(), user_vars, nsis_version);
327+
state.user_variables[0] = state.get_string(name.get());
334328
}
335329
Self::StrLen { input, .. } => {
336-
user_vars[0] = nsis_string(strings_block, input.get(), user_vars, nsis_version);
330+
state.user_variables[0] = state.get_string(input.get());
337331
}
338332
Self::AssignVar {
339333
string_offset,
340334
max_length,
341335
start_position,
342336
..
343337
} => {
344-
let result =
345-
nsis_string(strings_block, string_offset.get(), user_vars, nsis_version);
338+
let result = state.get_string(string_offset.get());
346339
let mut start = start_position.get();
347-
let mut new_len = 0;
348-
let src_len = result.len() as i32;
340+
let mut new_length = 0;
349341
if max_length.get() & !i32::from(u16::MAX) == 0 {
350-
new_len = src_len;
342+
new_length = result.len();
351343
}
352-
if new_len != 0 {
344+
if new_length != 0 {
353345
if start < 0 {
354-
start += src_len;
346+
start += result.len() as i32;
355347
}
356348

357-
start = start.clamp(0, src_len);
358-
if start < src_len {
359-
user_vars[0] = match result {
349+
let start = u32::try_from(start).unwrap_or_default();
350+
if start < result.len() as u32 {
351+
state.user_variables[0] = match result {
360352
Cow::Borrowed(borrowed) => Cow::Borrowed(&borrowed[start as usize..]),
361353
Cow::Owned(mut owned) => {
362354
owned.drain(..start as usize);
@@ -370,12 +362,7 @@ impl Entry {
370362
string_with_env_variables,
371363
..
372364
} => {
373-
user_vars[0] = nsis_string(
374-
strings_block,
375-
string_with_env_variables.get(),
376-
user_vars,
377-
nsis_version,
378-
);
365+
state.user_variables[0] = state.get_string(string_with_env_variables.get());
379366
}
380367
Self::IntOp {
381368
input1,
@@ -400,7 +387,7 @@ impl Entry {
400387
13 => ((input1.get() as u32).wrapping_shr(input2.get() as u32)) as i32,
401388
_ => input1.get(),
402389
};
403-
user_vars[0] = Cow::Owned(result.to_string());
390+
state.user_variables[0] = Cow::Owned(result.to_string());
404391
}
405392
_ => {}
406393
}

src/installers/nsis/language/table.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ use zerocopy::{FromBytes, Immutable, KnownLayout};
88
#[derive(Debug, FromBytes, KnownLayout, Immutable)]
99
#[repr(C)]
1010
pub struct LanguageTable {
11-
pub language_id: U16,
11+
pub id: U16,
1212
dialog_offset: U32,
1313
right_to_left: U32,
14-
pub language_string_offsets: [U32],
14+
pub string_offsets: [U32],
1515
}
1616

1717
const EN_US_LANG_CODE: U16 = U16::new(1033);
@@ -22,7 +22,7 @@ impl LanguageTable {
2222
.get(data, &header.blocks)
2323
.chunks_exact(header.langtable_size.get() as usize)
2424
.flat_map(Self::ref_from_bytes)
25-
.find_or_first(|lang_table| lang_table.language_id == EN_US_LANG_CODE)
25+
.find_or_first(|lang_table| lang_table.id == EN_US_LANG_CODE)
2626
.ok_or_else(|| Error::new(ErrorKind::NotFound, "No NSIS language table found"))
2727
}
2828
}

src/installers/nsis/mod.rs

Lines changed: 82 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod entry;
22
mod first_header;
33
mod header;
44
mod language;
5+
mod state;
56
mod strings;
67
mod version;
78

@@ -10,8 +11,6 @@ use crate::installers::nsis::entry::Entry;
1011
use crate::installers::nsis::first_header::FirstHeader;
1112
use crate::installers::nsis::header::compression::Compression;
1213
use crate::installers::nsis::header::{Decompressed, Header};
13-
use crate::installers::nsis::language::table::LanguageTable;
14-
use crate::installers::nsis::version::NsisVersion;
1514
use crate::installers::traits::InstallSpec;
1615
use crate::installers::utils::{read_lzma_stream_header, RELATIVE_PROGRAM_FILES_64};
1716
use crate::manifests::installer_manifest::Scope;
@@ -26,11 +25,11 @@ use header::block::BlockType;
2625
use liblzma::read::XzDecoder;
2726
use msi::Language;
2827
use protobuf::Enum;
28+
use state::NsisState;
2929
use std::borrow::Cow;
3030
use std::io;
3131
use std::io::Read;
3232
use std::str::FromStr;
33-
use strings::encoding::nsis_string;
3433
use strsim::levenshtein;
3534
use thiserror::Error;
3635
use versions::Versioning;
@@ -86,41 +85,33 @@ impl Nsis {
8685
let (header, _) = Header::ref_from_prefix(&decompressed_data)
8786
.map_err(|error| NsisError::ZeroCopy(error.to_string()))?;
8887

89-
let strings_block = BlockType::Strings.get(&decompressed_data, &header.blocks);
90-
91-
let language_table = LanguageTable::get_main(&decompressed_data, header)?;
92-
93-
let nsis_version = NsisVersion::from_manifest(data, pe)
94-
.or_else(|| NsisVersion::from_branding_text(strings_block, language_table))
95-
.unwrap_or_else(|| NsisVersion::detect(strings_block));
88+
let mut state = NsisState::new(pe, &decompressed_data, header)?;
9689

9790
let entries = <[Entry]>::try_ref_from_bytes(
9891
BlockType::Entries.get(&decompressed_data, &header.blocks),
9992
)
10093
.map_err(|error| NsisError::ZeroCopy(error.to_string()))?;
10194

102-
let mut user_vars = [const { Cow::Borrowed("") }; 9];
103-
10495
let mut architecture = None;
10596

10697
let mut display_name = None;
10798
let mut display_version = None;
10899
let mut display_publisher = None;
109100
for entry in entries {
110-
entry.update_vars(strings_block, &mut user_vars, nsis_version);
101+
entry.update_vars(&mut state);
111102
if let Entry::WriteReg {
112103
value_name, value, ..
113104
} = entry
114105
{
115-
let value = nsis_string(strings_block, value.get(), &user_vars, nsis_version);
116-
match &*nsis_string(strings_block, value_name.get(), &user_vars, nsis_version) {
106+
let value = state.get_string(value.get());
107+
match &*state.get_string(value_name.get()) {
117108
"DisplayName" => display_name = Some(value),
118109
"DisplayVersion" => display_version = Some(value),
119110
"Publisher" => display_publisher = Some(value),
120111
_ => {}
121112
}
122113
} else if let Entry::ExtractFile { name, .. } = entry {
123-
let name = nsis_string(strings_block, name.get(), &user_vars, nsis_version);
114+
let name = state.get_string(name.get());
124115
let file_stem = Utf8Path::new(&name).file_stem();
125116
// If there is an app-64 file, the app is x64.
126117
// If there is an app-32 file or both files are present, the app is x86
@@ -133,113 +124,94 @@ impl Nsis {
133124
};
134125
}
135126

136-
let install_dir = (header.install_directory_ptr != U32::ZERO).then(|| {
137-
nsis_string(
138-
strings_block,
139-
header.install_directory_ptr.get(),
140-
&user_vars,
141-
nsis_version,
142-
)
143-
});
144-
145-
let app_name = nsis_string(
146-
strings_block,
147-
language_table.language_string_offsets[2].get(),
148-
&user_vars,
149-
nsis_version,
150-
);
127+
let install_dir = (header.install_directory_ptr != U32::ZERO)
128+
.then(|| state.get_string(header.install_directory_ptr.get()));
151129

152-
architecture = architecture.or_else(|| {
153-
install_dir
154-
.as_deref()
155-
.is_some_and(|dir| dir.contains(RELATIVE_PROGRAM_FILES_64))
156-
.then_some(Architecture::X64)
157-
.or_else(|| {
158-
entries
159-
.iter()
160-
.filter_map(|entry| {
161-
if let Entry::ExtractFile { name, position, .. } = entry {
162-
Some((
163-
nsis_string(
164-
strings_block,
165-
name.get(),
166-
&user_vars,
167-
nsis_version,
168-
),
169-
position.get() as usize + size_of::<u32>(),
170-
))
171-
} else {
172-
None
173-
}
174-
})
175-
.filter(|(name, _)| {
176-
Utf8Path::new(name)
177-
.extension()
178-
.is_some_and(|extension| extension.eq_ignore_ascii_case(EXE))
179-
})
180-
.min_by_key(|(name, _)| levenshtein(name, &app_name))
181-
.map(|(_, mut position)| {
182-
if !is_solid {
183-
position += data_offset
184-
+ non_solid_start_offset as usize
185-
+ size_of::<u32>();
186-
}
187-
position
188-
})
189-
.and_then(|position| {
190-
let mut decoder: Box<dyn Read> = if is_solid {
191-
solid_decoder
192-
} else {
193-
match compression {
194-
Compression::Lzma(filter_flag) => {
195-
let mut data = &data[position + usize::from(filter_flag)..];
196-
let stream = read_lzma_stream_header(&mut data).ok()?;
197-
Box::new(XzDecoder::new_stream(data, stream))
198-
}
199-
Compression::BZip2 => {
200-
Box::new(BzDecoder::new(&data[position..]))
201-
}
202-
Compression::Zlib => {
203-
Box::new(DeflateDecoder::new(&data[position..]))
204-
}
205-
Compression::None => Box::new(&data[position..]),
130+
architecture = architecture
131+
.or_else(|| {
132+
install_dir
133+
.as_deref()
134+
.is_some_and(|dir| dir.contains(RELATIVE_PROGRAM_FILES_64))
135+
.then_some(Architecture::X64)
136+
})
137+
.or_else(|| {
138+
let app_name = state.get_string(state.language_table.string_offsets[2].get());
139+
entries
140+
.iter()
141+
.filter_map(|entry| {
142+
if let Entry::ExtractFile { name, position, .. } = entry {
143+
Some((
144+
state.get_string(name.get()),
145+
position.get().unsigned_abs() as usize + size_of::<u32>(),
146+
))
147+
} else {
148+
None
149+
}
150+
})
151+
.filter(|(name, _)| {
152+
Utf8Path::new(name)
153+
.extension()
154+
.is_some_and(|extension| extension.eq_ignore_ascii_case(EXE))
155+
})
156+
.min_by_key(|(name, _)| levenshtein(name, &app_name))
157+
.map(|(_, mut position)| {
158+
if !is_solid {
159+
position +=
160+
data_offset + non_solid_start_offset as usize + size_of::<u32>();
161+
}
162+
position
163+
})
164+
.and_then(|position| {
165+
let mut decoder: Box<dyn Read> = if is_solid {
166+
solid_decoder
167+
} else {
168+
match compression {
169+
Compression::Lzma(filter_flag) => {
170+
let mut data = &data[position + usize::from(filter_flag)..];
171+
let stream = read_lzma_stream_header(&mut data).ok()?;
172+
Box::new(XzDecoder::new_stream(data, stream))
206173
}
207-
};
208-
let mut void = io::sink();
209-
210-
if is_solid {
211-
// Seek to file
212-
io::copy(&mut decoder.by_ref().take(position as u64), &mut void)
213-
.ok()?;
174+
Compression::BZip2 => Box::new(BzDecoder::new(&data[position..])),
175+
Compression::Zlib => {
176+
Box::new(DeflateDecoder::new(&data[position..]))
177+
}
178+
Compression::None => Box::new(&data[position..]),
214179
}
180+
};
181+
let mut void = io::sink();
182+
183+
if is_solid {
184+
// Seek to file
185+
io::copy(&mut decoder.by_ref().take(position as u64), &mut void)
186+
.ok()?;
187+
}
215188

216-
// Seek to COFF header offset inside exe
217-
io::copy(&mut decoder.by_ref().take(0x3C), &mut void).ok()?;
189+
// Seek to COFF header offset inside exe
190+
io::copy(&mut decoder.by_ref().take(0x3C), &mut void).ok()?;
218191

219-
let coff_offset = decoder.read_u32::<LE>().ok()?;
192+
let coff_offset = decoder.read_u32::<LE>().ok()?;
220193

221-
// Seek to machine value
222-
io::copy(
223-
&mut decoder
224-
.by_ref()
225-
.take(u64::from(coff_offset.checked_sub(0x3C)?)),
226-
&mut void,
227-
)
228-
.ok()?;
194+
// Seek to machine value
195+
io::copy(
196+
&mut decoder
197+
.by_ref()
198+
.take(u64::from(coff_offset.checked_sub(0x3C)?)),
199+
&mut void,
200+
)
201+
.ok()?;
229202

230-
let machine_value = decoder.read_u16::<LE>().ok()?;
231-
Machine::from_i32(i32::from(machine_value))
232-
})
233-
.and_then(|machine| Architecture::from_machine(machine).ok())
234-
})
235-
});
203+
let machine_value = decoder.read_u16::<LE>().ok()?;
204+
Machine::from_i32(i32::from(machine_value))
205+
})
206+
.and_then(|machine| Architecture::from_machine(machine).ok())
207+
});
236208

237209
Ok(Self {
238210
architecture,
239211
scope: install_dir.as_deref().and_then(Scope::from_install_dir),
240212
install_dir: install_dir.as_deref().map(Utf8PathBuf::from),
241213
install_locale: LanguageTag::from_str(
242-
Language::from_code(language_table.language_id.get()).tag(),
214+
Language::from_code(state.language_table.id.get()).tag(),
243215
)
244216
.ok(),
245217
display_name: display_name.map(Cow::into_owned),

0 commit comments

Comments
 (0)