diff --git a/Cargo.toml b/Cargo.toml index b1f9f0c..345b8f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ resolver = "2" [workspace.package] version = "1.2.0" edition = "2024" -rust-version = "1.85" +rust-version = "1.98.1" license = "AGPL-3.0-only" [workspace.dependencies] diff --git a/docs/development.md b/docs/development.md index 2c8aa0b..8b7d277 100644 --- a/docs/development.md +++ b/docs/development.md @@ -2,7 +2,7 @@ ## Building -The pinned Rust toolchain is defined in `rust-toolchain.toml`. Build the CLI with `cargo build --release`; the binary is written to `target/release/senbei.exe` on Windows. +Rust 1.98.1 is required and pinned in `rust-toolchain.toml`. Build the CLI with `cargo build --release`; the binary is written to `target/release/senbei.exe` on Windows. The workspace crates are portable where their APIs are pure. The browser binding is outside the workspace and is checked with `cargo check --manifest-path senbei-wasm/Cargo.toml` or built with `wasm-pack`. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f45a9a0..c08f3dd 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "stable" +channel = "1.98.1" targets = ["x86_64-pc-windows-msvc", "wasm32-unknown-unknown"] diff --git a/senbei-crypto/src/android/protector.rs b/senbei-crypto/src/android/protector.rs index 2c962c7..8e5fb0a 100644 --- a/senbei-crypto/src/android/protector.rs +++ b/senbei-crypto/src/android/protector.rs @@ -552,7 +552,7 @@ pub fn transform_segment( let mut state = seed; let mut left = 0xe34e_ac63_u32; let mut right = 0x07b4_8238_u32; - for (index, chunk) in transformed.chunks_exact_mut(4).enumerate() { + for (index, chunk) in transformed.as_chunks_mut::<4>().0.iter_mut().enumerate() { let index32 = u32::try_from(index) .map_err(|_| Error::Invalid("segment word index exceeds u32".to_owned()))?; left = state @@ -564,10 +564,7 @@ pub fn transform_segment( .wrapping_add(right.wrapping_sub(0x1605_a81c).wrapping_mul(right)) .wrapping_shl(index32 & 7); state = left ^ right; - let bytes: [u8; 4] = chunk - .try_into() - .map_err(|_| Error::Invalid("invalid transformed word".to_owned()))?; - let mut value = u32::from_le_bytes(bytes); + let mut value = u32::from_le_bytes(*chunk); value = value.wrapping_add(0xb43b_9baf_u32.wrapping_mul(index32 & 0x0d)); value ^= 0xaf57_f7fb_u32.wrapping_mul(index32 & 3); value = value.wrapping_sub(state) ^ state; @@ -577,13 +574,10 @@ pub fn transform_segment( if decrypt_aes { let cipher = Aes256::new_from_slice(aes_key) .map_err(|_| Error::Invalid("invalid AES-256 key length".to_owned()))?; - let aligned_size = transformed.len() & !0x0f; let mut previous = [0_u8; 16]; - for chunk in transformed[..aligned_size].chunks_exact_mut(16) { - let mut ciphertext = [0_u8; 16]; - ciphertext.copy_from_slice(chunk); - // chunk is exactly one block (chunks_exact_mut(16)). - cipher.decrypt_block(chunk.try_into().expect("chunk is one block")); + for chunk in transformed.as_chunks_mut::<16>().0 { + let ciphertext = *chunk; + cipher.decrypt_block((&mut *chunk).into()); for (byte, prior) in chunk.iter_mut().zip(previous) { *byte ^= prior; } diff --git a/senbei-engine/src/android/extract/stage1.rs b/senbei-engine/src/android/extract/stage1.rs index 7e2ce5a..d18600d 100644 --- a/senbei-engine/src/android/extract/stage1.rs +++ b/senbei-engine/src/android/extract/stage1.rs @@ -177,18 +177,14 @@ fn decrypt_header(raw: &[u8], constant: u32) -> Result { } fn decrypt_words(ciphertext: &[u8], key: u32, constant: u32) -> Result> { - if ciphertext.len() % 4 != 0 { + if !ciphertext.len().is_multiple_of(4) { return invalid("Stage 1 word cipher input is not 4-byte aligned"); } let mut plaintext = ciphertext.to_vec(); - for (index, chunk) in plaintext.chunks_exact_mut(4).enumerate() { + for (index, chunk) in plaintext.as_chunks_mut::<4>().0.iter_mut().enumerate() { let index = u32::try_from(index) .map_err(|_| Error::Invalid("Stage 1 word index exceeds u32".to_owned()))?; - let mut word = u32::from_le_bytes( - chunk - .try_into() - .map_err(|_| Error::Invalid("Stage 1 word has an invalid size".to_owned()))?, - ); + let mut word = u32::from_le_bytes(*chunk); word = word.wrapping_add(index.wrapping_add(3).wrapping_mul(key)); word ^= constant.wrapping_mul(index.wrapping_add(1)); chunk.copy_from_slice(&word.to_le_bytes()); diff --git a/senbei-engine/src/android/extract/stream.rs b/senbei-engine/src/android/extract/stream.rs index f693442..6f87f59 100644 --- a/senbei-engine/src/android/extract/stream.rs +++ b/senbei-engine/src/android/extract/stream.rs @@ -130,13 +130,9 @@ fn decrypt_record(raw: &[u8], index: usize, state: u32) -> Result { let mut accumulator = 0x7993_4cf6_u32; let mut feedback = 0xf02f_7685_u32; let mut words = [0_u32; RECORD_SIZE / 4]; - for (word_index, chunk) in raw.chunks_exact(4).enumerate() { + for (word_index, chunk) in raw.as_chunks::<4>().0.iter().enumerate() { feedback = feedback.wrapping_mul(feedback); - let cipher = u32::from_le_bytes( - chunk - .try_into() - .map_err(|_| Error::Invalid("record word has an invalid size".to_owned()))?, - ); + let cipher = u32::from_le_bytes(*chunk); let mut value = gf32_mul_fixed(cipher ^ (feedback >> 3)) ^ index_mask; value = value.wrapping_add(accumulator).wrapping_add(state); value = value.wrapping_sub(mix >> ((word_index * 4 + 3) & 5)); diff --git a/senbei-engine/src/android/restore/pipeline.rs b/senbei-engine/src/android/restore/pipeline.rs index 5cbcc96..482bdce 100644 --- a/senbei-engine/src/android/restore/pipeline.rs +++ b/senbei-engine/src/android/restore/pipeline.rs @@ -477,7 +477,8 @@ fn restore_hidden_symbols( dynstr: SectionHeader, patch_data: &[u8], ) -> Result<(Vec, Vec, HiddenSymbolReport)> { - if dynsym.entry_size != ELF64_SYMBOL_SIZE as u64 || dynsym.size % ELF64_SYMBOL_SIZE as u64 != 0 + if dynsym.entry_size != ELF64_SYMBOL_SIZE as u64 + || !dynsym.size.is_multiple_of(ELF64_SYMBOL_SIZE as u64) { return invalid("unexpected .dynsym entry layout"); } @@ -584,11 +585,13 @@ fn restore_hidden_symbols( } fn dynamic_symbol_names(symbols: &[u8], strings: &[u8]) -> Result>> { - if symbols.len() % ELF64_SYMBOL_SIZE != 0 { + if !symbols.len().is_multiple_of(ELF64_SYMBOL_SIZE) { return invalid("dynamic symbol table is not entry-aligned"); } symbols - .chunks_exact(ELF64_SYMBOL_SIZE) + .as_chunks::() + .0 + .iter() .map(|symbol| { let name_offset = read_u32(symbol, 0)? as usize; Ok(read_c_string(strings, name_offset, strings.len())?.to_vec()) @@ -698,7 +701,7 @@ fn patch_dynamic_tags( dynamic: SectionHeader, values: &BTreeMap, ) -> Result<()> { - if dynamic.size % 0x10 != 0 { + if !dynamic.size.is_multiple_of(0x10) { return invalid(".dynamic size is not entry-aligned"); } let start = usize_from_u64(dynamic.offset, ".dynamic offset")?; @@ -732,7 +735,7 @@ fn patch_dynamic_tags( } fn dynamic_contains_tag(output: &[u8], dynamic: SectionHeader, wanted: u64) -> Result { - if dynamic.size % 0x10 != 0 { + if !dynamic.size.is_multiple_of(0x10) { return invalid(".dynamic size is not entry-aligned"); } let start = usize_from_u64(dynamic.offset, ".dynamic offset")?; @@ -1418,13 +1421,12 @@ fn validate_restored_binary( rela_plt.size / ELF64_RELA_SIZE as u64, "restored PLT relocation count", )?; - if let Some(expected) = materialization { - if dynamic_symbols != expected.new_symbol_count + if let Some(expected) = materialization + && (dynamic_symbols != expected.new_symbol_count || dynamic_relocations != expected.rela_dyn_count - || pltgot_relocations != expected.rela_plt_count - { - return invalid("restored ELF table counts do not match materialization report"); - } + || pltgot_relocations != expected.rela_plt_count) + { + return invalid("restored ELF table counts do not match materialization report"); } Ok(ValidationReport { format: "ELF64".to_owned(), diff --git a/senbei-engine/src/windows/exe/pipeline.rs b/senbei-engine/src/windows/exe/pipeline.rs index 24eca20..3b7b702 100644 --- a/senbei-engine/src/windows/exe/pipeline.rs +++ b/senbei-engine/src/windows/exe/pipeline.rs @@ -396,7 +396,7 @@ impl<'a> Unpacker<'a> { // non-critical for false-positive rejection. if v8 < info6 { let delta = info6.wrapping_sub(v8); - if delta <= 0x1000 && delta % 0x200 == 0 { + if delta <= 0x1000 && delta.is_multiple_of(0x200) { anchor = Some(probe); break; } diff --git a/senbei-wasm/Cargo.toml b/senbei-wasm/Cargo.toml index d296dc9..3f02557 100644 --- a/senbei-wasm/Cargo.toml +++ b/senbei-wasm/Cargo.toml @@ -2,6 +2,7 @@ name = "senbei-wasm" version = "1.2.0" edition = "2024" +rust-version = "1.98.1" description = "WebAssembly bindings for senbei (browser frontend assets live in web/)" license = "AGPL-3.0-only"