// Hex Editor — Signature Scanner
// Multi-pattern matching using Aho-Corasick automaton O(n + m + z).
//
// Algorithm: builds a trie from all magic byte patterns, adds failure
// links via BFS, then walks the automaton over input in a single pass.
// Each match triggers per-format validators with confidence scoring.
// Results filtered for false positives inside container formats.
///|
pub struct ScanMatch {
offset : Int
name : String
mut size : Int
mut confidence : Int
} derive(Debug)
///|
/// Aho-Corasick automaton for multi-pattern matching
priv struct AcNode {
next : FixedArray[Int] // transition to child node
mut fail : Int // failure link
mut pat_idx : Int // pattern index, -1 = no output
}
///|
priv struct AcPattern {
bytes : Bytes
}
///|
fn build_ac(patterns : Array[Bytes]) -> (Array[AcNode], Array[AcPattern]) {
let nodes : Array[AcNode] = []
// Root node
nodes.push({ next: FixedArray::make(256, -1), fail: 0, pat_idx: -1 })
let ac_pats : Array[AcPattern] = []
// Build trie
for pi = 0; pi < patterns.length(); pi = pi + 1 {
let pat = patterns[pi]
let mut n = 0
for j = 0; j < pat.length(); j = j + 1 {
let b = pat[j].to_int()
if nodes[n].next[b] == -1 {
nodes[n].next[b] = nodes.length()
nodes.push({ next: FixedArray::make(256, -1), fail: 0, pat_idx: -1 })
}
n = nodes[n].next[b]
}
nodes[n].pat_idx = ac_pats.length()
ac_pats.push({ bytes: pat })
}
// Build failure links (BFS)
let queue : Array[Int] = []
// Level 1
for b = 0; b < 256; b = b + 1 {
let child = nodes[0].next[b]
if child != -1 {
nodes[child].fail = 0
queue.push(child)
} else {
nodes[0].next[b] = 0
}
}
let mut qi = 0
while qi < queue.length() {
let v = queue[qi]
qi = qi + 1
// If v has output, propagate output via fail link
let f = nodes[v].fail
if nodes[f].pat_idx != -1 && nodes[v].pat_idx == -1 {
nodes[v].pat_idx = nodes[f].pat_idx
}
for b = 0; b < 256; b = b + 1 {
let child = nodes[v].next[b]
if child != -1 {
nodes[child].fail = nodes[f].next[b]
queue.push(child)
} else {
nodes[v].next[b] = nodes[f].next[b]
}
}
}
(nodes, ac_pats)
}
///|
pub fn scan_signatures(bytes : Bytes) -> Array[ScanMatch] {
let len = bytes.length()
let results : Array[ScanMatch] = []
// Collect magic patterns from validators
let magics = [
b"\xFF\xD8\xFF", // JPEG
b"\x89\x50\x4E\x47", // PNG
b"\x47\x49\x46\x38", // GIF
b"\x42\x4D", // BMP
b"\x52\x49\x46\x46", // RIFF/WAV/AVI
b"\x50\x4B\x03\x04", // ZIP
b"\x52\x61\x72\x21", // RAR
b"\x37\x7A\xBC\xAF\x27\x1C", // 7z
b"\x1F\x8B", // GZip
b"\x78\x9C", b"\x78\xDA", b"\x78\x01", b"\x78\x5E", // Zlib variants
b"\x42\x5A\x68", // BZip2
b"\x4D\x5A", // PE (exe/dll)
b"\x7F\x45\x4C\x46", // ELF
b"\x75\x73\x74\x61\x72", // TAR (ustar)
b"\x4F\x67\x67\x53", // OGG (OggS)
b"\x49\x44\x33", // MP3 (ID3)
b"\x66\x4C\x61\x43", // FLAC (fLaC)
b"\x1A\x45\xDF\xA3", // WebM (EBML)
]
let (nodes, ac_pats) = build_ac(magics)
// Walk AC automaton through data
let mut state = 0
let mut off = 0
while off < len && results.length() < 2000 {
let b = bytes[off].to_int()
state = nodes[state].next[b]
if nodes[state].pat_idx != -1 {
let pi = nodes[state].pat_idx
if pi < ac_pats.length() {
let p = ac_pats[pi]
let match_off = off - p.bytes.length() + 1
if match_off >= 0 {
check_sigs(bytes, match_off, len, results)
}
}
// Follow fail links for additional patterns
let mut fs = nodes[state].fail
while nodes[fs].pat_idx != -1 {
let fpi = nodes[fs].pat_idx
if fpi < ac_pats.length() {
let fp = ac_pats[fpi]
let fmatch_off = off - fp.bytes.length() + 1
if fmatch_off >= 0 {
check_sigs(bytes, fmatch_off, len, results)
}
}
fs = nodes[fs].fail
}
}
off = off + 1
}
let filtered = filter_false_positives(bytes, results)
let trusted : Array[ScanMatch] = []
for i = 0; i < filtered.length(); i = i + 1 {
if filtered[i].confidence >= 2 {
trusted.push(filtered[i])
}
}
estimate_sizes(bytes, trusted)
// Dedup: suppress compressed-format matches nested inside earlier payloads
let deduped : Array[ScanMatch] = []
for i = 0; i < trusted.length(); i = i + 1 {
let m = trusted[i]
let is_compressed = m.name.contains("Zlib") ||
m.name.contains("GZip") ||
m.name.contains("BZip2")
let mut nested = false
if is_compressed {
for j = 0; j < i; j = j + 1 {
let prev = trusted[j]
if m.offset > prev.offset && m.offset < prev.offset + prev.size {
nested = true
}
}
}
if !nested {
deduped.push(m)
}
}
// Tail detection: boost header confidence when end markers found
if len >= 2 &&
bytes[len - 2].to_int() == 0xFF &&
bytes[len - 1].to_int() == 0xD9 &&
bytes[0].to_int() == 0xFF &&
bytes[1].to_int() == 0xD8 {
for i = 0; i < deduped.length(); i = i + 1 {
if deduped[i].name.contains("JPEG") && deduped[i].confidence < 3 {
deduped[i].confidence = 3
}
}
}
if len >= 12 &&
bytes[0].to_int() == 0x89 &&
bytes[1].to_int() == 0x50 &&
bytes[len - 12].to_int() == 0x00 &&
bytes[len - 11].to_int() == 0x00 &&
bytes[len - 10].to_int() == 0x00 &&
bytes[len - 9].to_int() == 0x00 &&
bytes[len - 8].to_int() == 0x49 &&
bytes[len - 7].to_int() == 0x45 &&
bytes[len - 6].to_int() == 0x4E &&
bytes[len - 5].to_int() == 0x44 {
for i = 0; i < deduped.length(); i = i + 1 {
if deduped[i].name.contains("PNG image") && deduped[i].confidence < 3 {
deduped[i].confidence = 3
}
}
}
if len >= 3 &&
read_str(bytes, 0, 3) == "GIF" &&
bytes[len - 1].to_int() == 0x3B {
for i = 0; i < deduped.length(); i = i + 1 {
if deduped[i].name.contains("GIF image") && deduped[i].confidence < 3 {
deduped[i].confidence = 3
}
}
}
// Trailing data
if deduped.length() > 0 {
let last = deduped[deduped.length() - 1]
let tail_off = last.offset + last.size
if tail_off > 0 && tail_off + 256 < len {
deduped.push({
offset: tail_off,
name: "Trailing data (" + format_size(len - tail_off) + ")",
size: len - tail_off,
confidence: 1,
})
}
}
deduped
}
///|
/// Each format has a dedicated validator (binwalk-style per-signature parser)
fn validate_jpeg(bytes : Bytes, off : Int, len : Int) -> ScanMatch? {
guard off + 4 <= len &&
bytes[off].to_int() == 0xFF &&
bytes[off + 1].to_int() == 0xD8 &&
bytes[off + 2].to_int() == 0xFF else {
None
}
let marker = bytes[off + 3].to_int()
guard (marker >= 0xE0 && marker <= 0xEF) ||
marker == 0xFE ||
marker == 0xDB ||
(marker >= 0xC0 && marker <= 0xC4) else {
None
}
// Verify JPEG structure: require JFIF/EXIF identifier or valid marker chain
let end = if off + 300 < len { off + 300 } else { len }
let mut markers = 0
let mut pos = off + 2
while pos + 4 <= end {
if bytes[pos].to_int() != 0xFF {
pos = pos + 1
continue
}
let nm = bytes[pos + 1].to_int()
if nm == 0x00 {
pos = pos + 2
continue
} // escaped FF
if nm == 0xD9 {
break
} // EOI
if nm == 0xD8 {
pos = pos + 2
continue
} // nested SOI
// Valid marker: check segment length is sane
if pos + 4 > end {
break
}
let seg_len = bytes[pos + 2].to_int() * 256 + bytes[pos + 3].to_int()
if seg_len < 2 || pos + 2 + seg_len > len {
pos = pos + 2
continue
}
// Count structural markers
if nm == 0xE0 &&
seg_len >= 7 &&
bytes[pos + 4].to_int() == 0x4A &&
bytes[pos + 5].to_int() == 0x46 &&
bytes[pos + 6].to_int() == 0x49 &&
bytes[pos + 7].to_int() == 0x46 {
markers = markers + 1
} // JFIF
if nm == 0xE1 &&
seg_len >= 8 &&
bytes[pos + 4].to_int() == 0x45 &&
bytes[pos + 5].to_int() == 0x78 &&
bytes[pos + 6].to_int() == 0x69 &&
bytes[pos + 7].to_int() == 0x66 {
markers = markers + 1
} // Exif
if nm == 0xDB || (nm >= 0xC0 && nm <= 0xC2) {
markers = markers + 1
} // DQT or SOF
if markers >= 1 {
break
}
pos = pos + 2 + seg_len
}
guard markers >= 1 else { None }
Some({ offset: off, name: "JPEG image", size: 0, confidence: 3 })
}
///|
fn validate_png(bytes : Bytes, off : Int, len : Int) -> ScanMatch? {
guard off + 25 <= len &&
bytes[off].to_int() == 0x89 &&
bytes[off + 1].to_int() == 0x50 &&
bytes[off + 2].to_int() == 0x4E &&
bytes[off + 3].to_int() == 0x47 &&
bytes[off + 4].to_int() == 0x0D &&
bytes[off + 5].to_int() == 0x0A &&
bytes[off + 6].to_int() == 0x1A &&
bytes[off + 7].to_int() == 0x0A else {
None
}
guard read_u32_be(bytes, off + 8) == 0x0D &&
read_str(bytes, off + 12, 4) == "IHDR" else {
None
}
let w = read_u32_be(bytes, off + 16)
let h = read_u32_be(bytes, off + 20)
// Validate IHDR: color type and bit depth must be a valid combination
let bpp = bytes[off + 24].to_int()
let color_type = bytes[off + 25].to_int()
let valid_bpp_ct = match color_type {
0 => bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8 || bpp == 16
2 => bpp == 8 || bpp == 16
3 => bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8
4 => bpp == 8 || bpp == 16
6 => bpp == 8 || bpp == 16
_ => false
}
guard valid_bpp_ct else { None }
Some({
offset: off,
name: "PNG image, " + w.to_string() + " x " + h.to_string(),
size: 0,
confidence: 3,
})
}
///|
fn validate_gif(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 6 <= _len &&
(read_str(bytes, off, 6) == "GIF87a" || read_str(bytes, off, 6) == "GIF89a") else {
None
}
Some({ offset: off, name: "GIF image", size: 0, confidence: 3 })
}
///|
fn validate_bmp(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 30 <= _len &&
bytes[off].to_int() == 0x42 &&
bytes[off + 1].to_int() == 0x4D else {
None
}
let hdr_size = bytes[off + 14].to_int() +
bytes[off + 15].to_int() * 256 +
bytes[off + 16].to_int() * 65536 +
bytes[off + 17].to_int() * 16777216
let planes = bytes[off + 26].to_int() + bytes[off + 27].to_int() * 256
let bpp = bytes[off + 28].to_int() + bytes[off + 29].to_int() * 256
guard hdr_size >= 12 && hdr_size <= 256 else { None }
guard planes == 1 && bpp > 0 && bpp <= 32 else { None }
Some({ offset: off, name: "BMP image", size: 0, confidence: 3 })
}
///|
fn validate_wav(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 12 <= _len && read_str(bytes, off, 4) == "RIFF" else { None }
let fcc = read_str(bytes, off + 8, 4)
if fcc == "WAVE" {
return Some({ offset: off, name: "WAV audio", size: 0, confidence: 2 })
}
if fcc == "AVI " {
return Some({ offset: off, name: "AVI video", size: 0, confidence: 2 })
}
None
}
///|
fn validate_zip(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 30 <= _len &&
bytes[off].to_int() == 0x50 &&
bytes[off + 1].to_int() == 0x4B &&
bytes[off + 2].to_int() == 0x03 &&
bytes[off + 3].to_int() == 0x04 else {
None
}
let ver = bytes[off + 4].to_int() + bytes[off + 5].to_int() * 256
let flags = bytes[off + 6].to_int() + bytes[off + 7].to_int() * 256
let cm = bytes[off + 8].to_int() + bytes[off + 9].to_int() * 256
let name_len = bytes[off + 26].to_int() + bytes[off + 27].to_int() * 256
if ver <= 63 && cm <= 99 && (flags & 0xFFE0) == 0 && name_len <= 256 {
let cm_str = if cm == 0 {
"stored"
} else if cm == 8 {
"deflated"
} else {
"method=" + cm.to_string()
}
Some({ offset: off, name: "ZIP archive, " + cm_str, size: 0, confidence: 3 })
} else {
Some({
offset: off,
name: "ZIP archive (unverified)",
size: 0,
confidence: 1,
})
}
}
///|
fn validate_rar(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 7 <= _len && read_str(bytes, off, 4) == "Rar!" else { None }
Some({ offset: off, name: "RAR archive", size: 0, confidence: 3 })
}
///|
fn validate_7z(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 6 <= _len &&
bytes[off].to_int() == 0x37 &&
bytes[off + 1].to_int() == 0x7A &&
bytes[off + 2].to_int() == 0xBC &&
bytes[off + 3].to_int() == 0xAF &&
bytes[off + 4].to_int() == 0x27 &&
bytes[off + 5].to_int() == 0x1C else {
None
}
Some({ offset: off, name: "7-Zip archive", size: 0, confidence: 3 })
}
///|
fn validate_gzip(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 10 <= _len &&
bytes[off].to_int() == 0x1F &&
bytes[off + 1].to_int() == 0x8B else {
None
}
if bytes[off + 2].to_int() == 0x08 {
let flg = bytes[off + 3].to_int()
let extra = if (flg & 8) != 0 { ", has name" } else { "" }
Some({
offset: off,
name: "GZip compressed" + extra,
size: 0,
confidence: 3,
})
} else {
Some({ offset: off, name: "GZip (unverified)", size: 0, confidence: 1 })
}
}
///|
fn validate_zlib(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 2 <= _len else { None }
let header = bytes[off].to_int() * 256 + bytes[off + 1].to_int()
guard header == 0x789C ||
header == 0x78DA ||
header == 0x7801 ||
header == 0x785E else {
None
}
// RFC 1950 FCHECK: (CMF*256+FLG) % 31 == 0, compression method must be deflate (8)
let cmf = bytes[off].to_int()
let flg = bytes[off + 1].to_int()
guard cmf % 16 == 8 && (cmf * 256 + flg) % 31 == 0 else { None }
let flevel = flg / 64 % 4
let level_str = if flevel == 0 {
"fastest"
} else if flevel == 1 {
"fast"
} else if flevel == 2 {
"default"
} else {
"best"
}
Some({
offset: off,
name: "Zlib compressed data, " + level_str + " compression",
size: 0,
confidence: 3,
})
}
///|
fn validate_bzip2(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 4 <= _len &&
bytes[off].to_int() == 0x42 &&
bytes[off + 1].to_int() == 0x5A &&
bytes[off + 2].to_int() == 0x68 else {
None
}
let level = bytes[off + 3].to_int()
if level >= 0x31 && level <= 0x39 {
Some({
offset: off,
name: "BZip2 compressed, level " + (level - 0x30).to_string(),
size: 0,
confidence: 3,
})
} else {
Some({ offset: off, name: "BZip2 (unverified)", size: 0, confidence: 1 })
}
}
///|
fn validate_pe(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 64 <= _len &&
bytes[off].to_int() == 0x4D &&
bytes[off + 1].to_int() == 0x5A else {
None
}
let elfanew = bytes[off + 60].to_int() +
bytes[off + 61].to_int() * 256 +
bytes[off + 62].to_int() * 65536 +
bytes[off + 63].to_int() * 16777216
if elfanew > 0 &&
off + elfanew + 4 <= _len &&
bytes[off + elfanew].to_int() == 0x50 &&
bytes[off + elfanew + 1].to_int() == 0x45 &&
bytes[off + elfanew + 2].to_int() == 0x00 &&
bytes[off + elfanew + 3].to_int() == 0x00 {
Some({ offset: off, name: "PE executable", size: 0, confidence: 3 })
} else {
Some({ offset: off, name: "PE executable (MZ)", size: 0, confidence: 1 })
}
}
///|
fn validate_elf(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 4 <= _len &&
bytes[off].to_int() == 0x7F &&
bytes[off + 1].to_int() == 0x45 &&
bytes[off + 2].to_int() == 0x4C &&
bytes[off + 3].to_int() == 0x46 else {
None
}
Some({ offset: off, name: "ELF executable", size: 0, confidence: 3 })
}
///|
fn validate_tar(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
// ustar magic at offset 257 from block start; check alignment + full magic
guard off >= 257 && (off - 257) % 512 == 0 && off + 6 <= _len else { None }
guard read_str(bytes, off, 5) == "ustar" &&
(bytes[off + 5].to_int() == 0x00 || bytes[off + 5].to_int() == 0x20) else {
None
}
Some({ offset: off - 257, name: "TAR archive", size: 0, confidence: 3 })
}
///|
fn validate_ogg(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 4 <= _len && read_str(bytes, off, 4) == "OggS" else { None }
Some({ offset: off, name: "OGG audio", size: 0, confidence: 3 })
}
///|
fn validate_mp3(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 10 <= _len && read_str(bytes, off, 3) == "ID3" else { None }
// Validate ID3v2 header: version (2.2, 2.3, 2.4), flags, syncsafe size
let ver_major = bytes[off + 3].to_int()
let ver_minor = bytes[off + 4].to_int()
guard ver_major >= 2 && ver_major <= 4 && ver_minor <= 255 else { None }
let flags = bytes[off + 5].to_int()
guard flags % 16 == 0 else { None } // low 4 bits must be 0
// Syncsafe size: each byte uses 7 bits, highest bit must be 0
let mut syncsafe_ok = true
for i = 6; i < 10; i = i + 1 {
if bytes[off + i].to_int() > 0x7F {
syncsafe_ok = false
}
}
guard syncsafe_ok else { None }
Some({
offset: off,
name: "MP3 audio (ID3v2.\{ver_major})",
size: 0,
confidence: 3,
})
}
///|
fn validate_flac(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 4 <= _len && read_str(bytes, off, 4) == "fLaC" else { None }
Some({ offset: off, name: "FLAC audio", size: 0, confidence: 3 })
}
///|
fn validate_webm(bytes : Bytes, off : Int, _len : Int) -> ScanMatch? {
guard off + 4 <= _len &&
bytes[off].to_int() == 0x1A &&
bytes[off + 1].to_int() == 0x45 &&
bytes[off + 2].to_int() == 0xDF &&
bytes[off + 3].to_int() == 0xA3 else {
None
}
Some({ offset: off, name: "WebM/Matroska", size: 0, confidence: 3 })
}
///|
fn check_sigs(
bytes : Bytes,
off : Int,
len : Int,
results : Array[ScanMatch],
) -> Unit {
let parsers = [
validate_jpeg, validate_png, validate_gif, validate_bmp, validate_wav, validate_zip,
validate_rar, validate_7z, validate_gzip, validate_zlib, validate_bzip2, validate_pe,
validate_elf, validate_tar, validate_ogg, validate_mp3, validate_flac, validate_webm,
]
for i = 0; i < parsers.length(); i = i + 1 {
match parsers[i](bytes, off, len) {
Some(m) => results.push(m)
None => ()
}
}
}
///|
/// Filter false positives inside containers
fn filter_false_positives(
bytes : Bytes,
matches : Array[ScanMatch],
) -> Array[ScanMatch] {
let is_container = bytes.length() >= 4 &&
(
(bytes[0].to_int() == 0x42 && bytes[1].to_int() == 0x4D) || // BMP
(bytes[0].to_int() == 0xFF && bytes[1].to_int() == 0xD8) || // JPEG
(bytes[0].to_int() == 0x50 && bytes[1].to_int() == 0x4B) || // ZIP
(bytes[0].to_int() == 0x89 && bytes[1].to_int() == 0x50) || // PNG
(bytes[0].to_int() == 0x1F && bytes[1].to_int() == 0x8B) || // GZip
(bytes[0].to_int() == 0x4D && bytes[1].to_int() == 0x5A) || // PE
read_str(bytes, 0, 4) == "Rar!" ||
( // RAR
bytes[0].to_int() == 0x37 && bytes[1].to_int() == 0x7A
) || // 7z
read_str(bytes, 257, 5) == "ustar" || // TAR
read_str(bytes, 0, 4) == "OggS" || // OGG
read_str(bytes, 0, 4) == "fLaC" ||
( // FLAC
read_str(bytes, 0, 6) == "GIF87a" ||
read_str(bytes, 0, 6) == "GIF89a"
) || // GIF
read_str(bytes, 0, 3) == "ID3" ||
( // MP3
bytes[0].to_int() == 0x1A && bytes[1].to_int() == 0x45
) || // WebM/EBML
read_str(bytes, 0, 4) == "RIFF"
) // WAV/AVI
// MP4: check ftyp box at offset 4
let is_mp4 = bytes.length() >= 12 && read_u32_be(bytes, 4) == 0x66747970
let has_container = is_container || is_mp4
fn is_fp(m : ScanMatch) -> Bool {
m.confidence <= 1 ||
m.name.contains("PE executable (MZ)") ||
m.name.contains("Zlib") ||
m.name.contains("GZip") ||
m.name.contains("BZip2")
}
let filtered : Array[ScanMatch] = []
for i = 0; i < matches.length(); i = i + 1 {
let m = matches[i]
if !(has_container && m.offset > 0 && is_fp(m)) {
filtered.push(m)
}
}
filtered
}
///|
fn estimate_sizes(bytes : Bytes, matches : Array[ScanMatch]) -> Unit {
let len = bytes.length()
let total = matches.length()
for i = 0; i < total; i = i + 1 {
let end = if i + 1 < total { matches[i + 1].offset } else { len }
matches[i].size = end - matches[i].offset
}
}
///|
pub fn extract_region(
bytes : Bytes,
start : Int,
name : String,
base_path : String,
) -> String {
let len = bytes.length()
guard start >= 0 && start < len else { "Invalid offset" }
let count = len - start
guard count > 0 else { "Zero-length region" }
let data = Bytes::makei(count, i => bytes[start + i])
let ext = guess_ext(name)
let out_path = base_path + "_0x" + to_hex_string(start, width=8) + "." + ext
try {
@fs.write_bytes_to_file(out_path, data)
"Saved: " + out_path
} catch {
@fs.IOError::IOError(_) => "Save error"
}
}
///|
fn guess_ext(name : String) -> String {
if name.contains("JPEG") {
"jpg"
} else if name.contains("PNG") {
"png"
} else if name.contains("GIF") {
"gif"
} else if name.contains("BMP") {
"bmp"
} else if name.contains("WAV") {
"wav"
} else if name.contains("ZIP") {
"zip"
} else if name.contains("RAR") {
"rar"
} else if name.contains("7-Zip") {
"7z"
} else if name.contains("GZip") {
"gz"
} else if name.contains("Zlib") {
"zlib"
} else if name.contains("PE") {
"exe"
} else if name.contains("ELF") {
"elf"
} else if name.contains("PDF") {
"pdf"
} else if name.contains("SQLite") {
"db"
} else {
"bin"
}
}