mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-09-19 03:57:59 -04:00
Compare commits
5
Commits
f862633512
..
v1.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6224e639c | ||
|
|
9b8024b132 | ||
|
|
512a627066 | ||
|
|
a230c37281 | ||
|
|
4f59e25652 |
Generated
+7
-7
@@ -402,7 +402,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-cli"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"senbei-engine",
|
||||
"senbei-io",
|
||||
@@ -413,7 +413,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-crypto"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"thiserror",
|
||||
@@ -421,7 +421,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-elf"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"goblin",
|
||||
"thiserror",
|
||||
@@ -429,7 +429,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-engine"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"memmap2",
|
||||
"senbei-crypto",
|
||||
@@ -444,7 +444,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-io"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indicatif",
|
||||
@@ -465,7 +465,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-metadata"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -473,7 +473,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-pe"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ exclude = ["senbei-wasm"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
edition = "2024"
|
||||
rust-version = "1.98.1"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[toolchain]
|
||||
channel = "1.98.1"
|
||||
# CI invokes rustfmt/clippy through the rustup shim with no setup action, so
|
||||
# the pinned toolchain must declare its components here — the runner images
|
||||
# only preinstall them for their default toolchain.
|
||||
components = ["rustfmt", "clippy"]
|
||||
targets = ["x86_64-pc-windows-msvc", "wasm32-unknown-unknown"]
|
||||
|
||||
@@ -976,6 +976,58 @@ impl<'a> Unpacker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Re-arm TLS field base relocations (PE32 DLL) ----
|
||||
// The packer neutralizes the four relocations covering the TLS
|
||||
// directory's VA fields (Start/EndAddressOfRawData, AddressOfIndex,
|
||||
// AddressOfCallBacks) by demoting them to IMAGE_REL_BASED_ABSOLUTE
|
||||
// padding, because its own loader fixes TLS up by hand. Restored
|
||||
// verbatim, a DLL mapped off its preferred base keeps stale VAs in its
|
||||
// TLS directory, and the OS loader faults in LdrpAllocateTlsEntry when
|
||||
// it writes the TLS slot index through the unrelocated AddressOfIndex.
|
||||
// Promote those entries back to IMAGE_REL_BASED_HIGHLOW. EXE output
|
||||
// is untouched: its BaseReloc directory is zeroed above, and its
|
||||
// goldens must stay byte-identical.
|
||||
if is_dll && tls_dir_rva > 0 {
|
||||
let reloc_rva = get_u32(&self.decompressed, exe_pe.wrapping_add(0xA0));
|
||||
let reloc_size = get_u32(&self.decompressed, exe_pe.wrapping_add(0xA4));
|
||||
let reloc_end = reloc_rva.wrapping_add(reloc_size);
|
||||
let dlen = self.decompressed.len() as u32;
|
||||
if reloc_rva > 0 && reloc_size >= 8 && reloc_end <= dlen {
|
||||
let mut rearmed = 0u32;
|
||||
let mut block = reloc_rva;
|
||||
while block.wrapping_add(8) <= reloc_end {
|
||||
let page = get_u32(&self.decompressed, block);
|
||||
let block_size = get_u32(&self.decompressed, block.wrapping_add(4));
|
||||
if block_size < 8
|
||||
|| !block_size.is_multiple_of(2)
|
||||
|| block.wrapping_add(block_size) > reloc_end
|
||||
{
|
||||
break;
|
||||
}
|
||||
let entries = (block_size - 8) / 2;
|
||||
for i in 0..entries {
|
||||
let entry_off = block.wrapping_add(8).wrapping_add(i.wrapping_mul(2));
|
||||
let entry = get_u16(&self.decompressed, entry_off);
|
||||
let entry_rva = page.wrapping_add((entry & 0x0FFF) as u32);
|
||||
// IMAGE_REL_BASED_ABSOLUTE with a real offset is
|
||||
// neutralized, not padding (padding keeps offset 0).
|
||||
if entry >> 12 == 0
|
||||
&& entry & 0x0FFF != 0
|
||||
&& entry_rva >= tls_dir_rva
|
||||
&& entry_rva < tls_dir_rva.wrapping_add(16)
|
||||
{
|
||||
write_u16(&mut self.decompressed, entry_off, (entry | 0x3000) as u32);
|
||||
rearmed = rearmed.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
block = block.wrapping_add(block_size);
|
||||
}
|
||||
if verbose && rearmed > 0 {
|
||||
println!(" re-armed {} TLS field relocations", rearmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Import table (PE32, 4-byte thunks) ----
|
||||
if verbose {
|
||||
println!("[8/9] Decrypting import strings (PE32)...");
|
||||
|
||||
Generated
+7
-7
@@ -412,7 +412,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-crypto"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"thiserror",
|
||||
@@ -420,7 +420,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-elf"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"goblin",
|
||||
"thiserror",
|
||||
@@ -428,7 +428,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-engine"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"memmap2",
|
||||
"senbei-crypto",
|
||||
@@ -443,7 +443,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-io"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indicatif",
|
||||
@@ -464,7 +464,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-metadata"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -472,14 +472,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "senbei-pe"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "senbei-wasm"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"senbei-engine",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "senbei-wasm"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
edition = "2024"
|
||||
rust-version = "1.98.1"
|
||||
description = "WebAssembly bindings for senbei (browser frontend assets live in web/)"
|
||||
|
||||
+31
-3
@@ -50,6 +50,22 @@ const KIND_LABEL = {
|
||||
'native-dll': 'protected native DLL',
|
||||
'managed-dll': 'protected managed DLL',
|
||||
metadata: 'il2cpp metadata',
|
||||
'android-package':
|
||||
'Android app package — the web build cannot unpack these yet; use the senbei CLI',
|
||||
'android-so':
|
||||
'Android AArch64 library — the web build cannot unpack these yet; use the senbei CLI',
|
||||
};
|
||||
|
||||
// Android targets are recognized by extension so the row can explain the
|
||||
// situation instead of reporting a protected file as unrecognized: the
|
||||
// Android pipeline is filesystem orchestration (senbei-io) with no wasm
|
||||
// build, so these files need the CLI. The pseudo-kinds are labels only —
|
||||
// they never reach the worker.
|
||||
const ANDROID_KIND = {
|
||||
apk: 'android-package',
|
||||
apks: 'android-package',
|
||||
xapk: 'android-package',
|
||||
so: 'android-so',
|
||||
};
|
||||
|
||||
const COMPANION_SVG =
|
||||
@@ -91,8 +107,13 @@ async function stageFiles(list) {
|
||||
// the PE header fields); read a small slice, not the whole file.
|
||||
const head = new Uint8Array(await file.slice(0, 65536).arrayBuffer());
|
||||
// Companions are ciphertext fragments; detect() only makes sense on the
|
||||
// base module, so skip it for `._` files.
|
||||
const kind = file.name.endsWith('._') ? undefined : detect(head);
|
||||
// base module, so skip it for `._` files. Android targets short-circuit
|
||||
// detect() as well: an ELF/zip never classifies as a protected PE, and
|
||||
// the row must carry the android pseudo-kind for its status line.
|
||||
const ext = file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase();
|
||||
const kind = file.name.endsWith('._')
|
||||
? undefined
|
||||
: (ANDROID_KIND[ext] ?? detect(head));
|
||||
const old = files.get(file.name);
|
||||
files.set(file.name, {
|
||||
file,
|
||||
@@ -284,7 +305,10 @@ function render() {
|
||||
|
||||
const unpackable = [...files].some(
|
||||
([name, e]) =>
|
||||
!name.endsWith('._') && e.kind !== undefined && e.state === 'staged',
|
||||
!name.endsWith('._') &&
|
||||
e.kind !== undefined &&
|
||||
!e.kind.startsWith('android-') &&
|
||||
e.state === 'staged',
|
||||
);
|
||||
unpackBtn.disabled = !unpackable;
|
||||
actions.hidden = files.size === 0;
|
||||
@@ -364,6 +388,10 @@ unpackBtn.addEventListener('click', async () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Android rows are informational only (no wasm pipeline); their staged
|
||||
// status line already says to use the CLI.
|
||||
if (entry.kind.startsWith('android-')) continue;
|
||||
|
||||
entry.state = 'working';
|
||||
render();
|
||||
try {
|
||||
|
||||
+3
-1
@@ -56,7 +56,9 @@
|
||||
<p class="hint">
|
||||
Protected <code>.exe</code> / <code>.dll</code> modules, optional
|
||||
<code>._</code> companions, or an il2cpp
|
||||
<code>global-metadata.dat</code>.
|
||||
<code>global-metadata.dat</code>. Android packages
|
||||
(<code>.apk</code> / <code>.apks</code> / <code>.xapk</code>) and
|
||||
<code>.so</code> libraries are recognized but need the CLI.
|
||||
</p>
|
||||
<input type="file" id="picker" multiple hidden>
|
||||
</div>
|
||||
|
||||
@@ -333,6 +333,26 @@ footer {
|
||||
}
|
||||
footer a { color: var(--dim); }
|
||||
|
||||
/* --- narrow screens --- */
|
||||
|
||||
/* On a narrow viewport the status column (flex: 1, right-aligned) is
|
||||
squeezed by a long file name into a one-word-per-line vertical strip.
|
||||
Stack the row instead: name + icons on the first line, the status on its
|
||||
own full-width line below, left-aligned. */
|
||||
@media (max-width: 560px) {
|
||||
main { padding: 1.5rem 0.9rem 2.5rem; }
|
||||
|
||||
#dropzone { padding: 1.75rem 1rem; }
|
||||
|
||||
.file .row { flex-wrap: wrap; }
|
||||
.file .name { flex: 1; min-width: 0; }
|
||||
.file .status {
|
||||
order: 6; /* after the download/remove icons */
|
||||
flex-basis: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- animations --- */
|
||||
|
||||
@keyframes fadeIn {
|
||||
|
||||
Reference in New Issue
Block a user