Puterboy1 Posted yesterday at 06:45 PM Posted yesterday at 06:45 PM I need a way to extract it's contents for a friend of mine: https://github.com/user-attachments/files/28807415/ALLDOGS.zip
Members zbirow Posted 22 hours ago Members Posted 22 hours ago 1 hour ago, Puterboy1 said: Potrzebuję sposobu na wyodrębnienie jego zawartości dla mojego znajomego: https://github.com/user-attachments/files/28807415/ALLDOGS.zip import argparse import struct import sys import threading from dataclasses import dataclass from pathlib import Path @dataclass(frozen=True) class EmbeddedFile: kind: str offset: int size: int extension: str label: str details: str @property def end(self) -> int: return self.offset + self.size def u16(data: bytes, offset: int) -> int: return struct.unpack_from("<H", data, offset)[0] def u32(data: bytes, offset: int) -> int: return struct.unpack_from("<I", data, offset)[0] def i32(data: bytes, offset: int) -> int: return struct.unpack_from("<i", data, offset)[0] def find_all(data: bytes, needle: bytes): pos = 0 while True: hit = data.find(needle, pos) if hit < 0: return yield hit pos = hit + 1 def parse_bmp(data: bytes, offset: int) -> EmbeddedFile | None: size = len(data) if offset + 54 > size or data[offset : offset + 2] != b"BM": return None file_size = u32(data, offset + 2) reserved_a = u16(data, offset + 6) reserved_b = u16(data, offset + 8) pixel_offset = u32(data, offset + 10) dib_size = u32(data, offset + 14) if not (26 <= file_size <= size - offset): return None if reserved_a != 0 or reserved_b != 0: return None if dib_size not in (12, 40, 52, 56, 108, 124): return None if pixel_offset < 14 + dib_size or pixel_offset > file_size: return None if dib_size == 12: width = u16(data, offset + 18) height = u16(data, offset + 20) planes = u16(data, offset + 22) bpp = u16(data, offset + 24) compression = 0 else: width = i32(data, offset + 18) height = i32(data, offset + 22) planes = u16(data, offset + 26) bpp = u16(data, offset + 28) compression = u32(data, offset + 30) abs_width = abs(width) abs_height = abs(height) if not (1 <= abs_width <= 10000 and 1 <= abs_height <= 10000): return None if planes != 1 or bpp not in (1, 4, 8, 16, 24, 32): return None if compression not in (0, 1, 2, 3, 4, 5, 6): return None label = f"{abs_width}x{abs_height}_{bpp}bpp" details = f"BMP {abs_width}x{abs_height}, {bpp} bpp, size {file_size}" return EmbeddedFile("bmp", offset, file_size, ".bmp", label, details) def parse_wave(data: bytes, offset: int) -> EmbeddedFile | None: size = len(data) if offset + 12 > size or data[offset : offset + 4] != b"RIFF": return None riff_size = u32(data, offset + 4) end = offset + 8 + riff_size if end > size or riff_size < 4 or data[offset + 8 : offset + 12] != b"WAVE": return None fmt_found = False data_found = False audio_format = 0 channels = 0 sample_rate = 0 bits_per_sample = 0 data_size = 0 pos = offset + 12 while pos + 8 <= end: chunk_id = data[pos : pos + 4] chunk_size = u32(data, pos + 4) chunk_data = pos + 8 chunk_end = chunk_data + chunk_size if chunk_end > end: break if chunk_id == b"fmt " and chunk_size >= 16: audio_format = u16(data, chunk_data) channels = u16(data, chunk_data + 2) sample_rate = u32(data, chunk_data + 4) bits_per_sample = u16(data, chunk_data + 14) fmt_found = True elif chunk_id == b"data": data_size = chunk_size data_found = True pos = chunk_end + (chunk_size & 1) if not (fmt_found and data_found): return None if not (1 <= channels <= 8 and 1000 <= sample_rate <= 384000): return None if bits_per_sample not in (4, 8, 12, 16, 24, 32): return None label = f"{channels}ch_{sample_rate}hz_{bits_per_sample}bit" details = ( f"WAVE format {audio_format}, {channels} ch, " f"{sample_rate} Hz, {bits_per_sample} bit, data {data_size}" ) return EmbeddedFile("wav", offset, end - offset, ".wav", label, details) def scan_data(data: bytes, include_bmp: bool = True, include_wav: bool = True) -> list[EmbeddedFile]: found: list[EmbeddedFile] = [] if include_bmp: for offset in find_all(data, b"BM"): item = parse_bmp(data, offset) if item: found.append(item) if include_wav: for offset in find_all(data, b"RIFF"): item = parse_wave(data, offset) if item: found.append(item) found.sort(key=lambda item: (item.offset, item.kind, item.size)) return found def output_name(source_stem: str, index: int, item: EmbeddedFile) -> str: clean_label = "".join(c if c.isalnum() or c in ("_", "-") else "_" for c in item.label) return f"{source_stem}_{index:04d}_{item.offset:08X}_{clean_label}{item.extension}" def extract_imx( input_path: Path, output_dir: Path, include_bmp: bool = True, include_wav: bool = True, use_subfolders: bool = True, ) -> tuple[list[EmbeddedFile], list[Path]]: data = input_path.read_bytes() items = scan_data(data, include_bmp=include_bmp, include_wav=include_wav) output_dir.mkdir(parents=True, exist_ok=True) written: list[Path] = [] for index, item in enumerate(items, 1): folder = output_dir / item.kind if use_subfolders else output_dir folder.mkdir(parents=True, exist_ok=True) path = folder / output_name(input_path.stem, index, item) path.write_bytes(data[item.offset : item.end]) written.append(path) return items, written def summarize(items: list[EmbeddedFile]) -> str: bmp_count = sum(1 for item in items if item.kind == "bmp") wav_count = sum(1 for item in items if item.kind == "wav") total_size = sum(item.size for item in items) lines = [ f"Found {len(items)} file(s): {bmp_count} BMP, {wav_count} WAVE.", f"Total extracted byte count: {total_size:,}", ] for item in items[:40]: lines.append(f"{item.kind.upper()} @ 0x{item.offset:08X}, {item.details}") if len(items) > 40: lines.append(f"... {len(items) - 40} more file(s)") return "\n".join(lines) def run_gui() -> None: import tkinter as tk from tkinter import filedialog, messagebox, scrolledtext, ttk root = tk.Tk() root.title("IMX BMP/WAVE Extractor") root.geometry("780x520") root.resizable(True, True) default_input = Path("ALLDOGS.IMX").resolve() if Path("ALLDOGS.IMX").exists() else Path.cwd() input_var = tk.StringVar(value=str(default_input)) output_var = tk.StringVar(value=str((Path.cwd() / "_alldogs_imx_extract").resolve())) bmp_var = tk.BooleanVar(value=True) wav_var = tk.BooleanVar(value=True) subfolders_var = tk.BooleanVar(value=True) status_var = tk.StringVar(value="Ready") main = ttk.Frame(root, padding=12) main.pack(fill="both", expand=True) main.columnconfigure(1, weight=1) main.rowconfigure(5, weight=1) def browse_input() -> None: path = filedialog.askopenfilename(title="Choose IMX file", filetypes=[("IMX files", "*.imx"), ("All files", "*.*")]) if path: input_var.set(path) output_var.set(str(Path(path).with_suffix("").with_name(Path(path).stem + "_extract"))) def browse_output() -> None: path = filedialog.askdirectory(title="Choose output folder") if path: output_var.set(path) def set_text(text: str) -> None: log.configure(state="normal") log.delete("1.0", "end") log.insert("1.0", text) log.configure(state="disabled") def selected_types() -> tuple[bool, bool]: include_bmp = bmp_var.get() include_wav = wav_var.get() if not include_bmp and not include_wav: raise ValueError("Select at least one file type.") return include_bmp, include_wav def scan_clicked() -> None: try: include_bmp, include_wav = selected_types() input_path = Path(input_var.get().strip()) if not input_path.exists(): raise ValueError("Input file does not exist.") data = input_path.read_bytes() items = scan_data(data, include_bmp=include_bmp, include_wav=include_wav) status_var.set(f"Scan complete: {len(items)} file(s).") set_text(summarize(items)) except Exception as exc: status_var.set("Scan failed") messagebox.showerror("Scan failed", str(exc)) def extract_clicked() -> None: def worker() -> None: try: include_bmp, include_wav = selected_types() input_path = Path(input_var.get().strip()) output_dir = Path(output_var.get().strip()) if not input_path.exists(): raise ValueError("Input file does not exist.") items, written = extract_imx( input_path=input_path, output_dir=output_dir, include_bmp=include_bmp, include_wav=include_wav, use_subfolders=subfolders_var.get(), ) text = summarize(items) + f"\n\nWrote {len(written)} file(s) to:\n{output_dir}" root.after(0, lambda: status_var.set(f"Extracted {len(written)} file(s).")) root.after(0, lambda: set_text(text)) root.after(0, lambda: messagebox.showinfo("Extract complete", f"Extracted {len(written)} file(s).")) except Exception as exc: root.after(0, lambda: status_var.set("Extract failed")) root.after(0, lambda: messagebox.showerror("Extract failed", str(exc))) status_var.set("Extracting...") threading.Thread(target=worker, daemon=True).start() ttk.Label(main, text="Input IMX").grid(row=0, column=0, sticky="w", padx=6, pady=6) ttk.Entry(main, textvariable=input_var).grid(row=0, column=1, sticky="ew", padx=6, pady=6) ttk.Button(main, text="Browse", command=browse_input).grid(row=0, column=2, padx=6, pady=6) ttk.Label(main, text="Output Folder").grid(row=1, column=0, sticky="w", padx=6, pady=6) ttk.Entry(main, textvariable=output_var).grid(row=1, column=1, sticky="ew", padx=6, pady=6) ttk.Button(main, text="Browse", command=browse_output).grid(row=1, column=2, padx=6, pady=6) options = ttk.Frame(main) options.grid(row=2, column=0, columnspan=3, sticky="w", padx=6, pady=8) ttk.Checkbutton(options, text="Extract BMP", variable=bmp_var).grid(row=0, column=0, padx=8, pady=4) ttk.Checkbutton(options, text="Extract WAVE", variable=wav_var).grid(row=0, column=1, padx=8, pady=4) ttk.Checkbutton(options, text="Use subfolders", variable=subfolders_var).grid(row=0, column=2, padx=8, pady=4) buttons = ttk.Frame(main) buttons.grid(row=3, column=0, columnspan=3, sticky="e", padx=6, pady=8) ttk.Button(buttons, text="Scan", command=scan_clicked).grid(row=0, column=0, padx=6) ttk.Button(buttons, text="Extract", command=extract_clicked).grid(row=0, column=1, padx=6) ttk.Label(main, textvariable=status_var).grid(row=4, column=0, columnspan=3, sticky="w", padx=6, pady=4) log = scrolledtext.ScrolledText(main, wrap="word", state="disabled") log.grid(row=5, column=0, columnspan=3, sticky="nsew", padx=6, pady=6) root.mainloop() def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Extract BMP and WAVE files from IMX containers.") parser.add_argument("input", nargs="?", help="Input .imx file.") parser.add_argument("-o", "--output", help="Output folder. Default: <input>_extract") parser.add_argument("--no-bmp", action="store_true", help="Do not extract BMP files.") parser.add_argument("--no-wav", action="store_true", help="Do not extract WAVE files.") parser.add_argument("--flat", action="store_true", help="Do not create bmp/wav subfolders.") parser.add_argument("--scan-only", action="store_true", help="Only scan and print a summary.") parser.add_argument("--gui", action="store_true", help="Open the graphical interface.") return parser.parse_args(argv) def main(argv: list[str] | None = None) -> int: args = parse_args(sys.argv[1:] if argv is None else argv) if args.gui or not args.input: run_gui() return 0 input_path = Path(args.input) output_dir = Path(args.output) if args.output else input_path.with_suffix("").with_name(input_path.stem + "_extract") include_bmp = not args.no_bmp include_wav = not args.no_wav if not include_bmp and not include_wav: raise SystemExit("Nothing to extract: both BMP and WAVE are disabled.") if args.scan_only: items = scan_data(input_path.read_bytes(), include_bmp=include_bmp, include_wav=include_wav) print(summarize(items)) return 0 items, written = extract_imx( input_path=input_path, output_dir=output_dir, include_bmp=include_bmp, include_wav=include_wav, use_subfolders=not args.flat, ) print(summarize(items)) print(f"\nWrote {len(written)} file(s) to {output_dir}") return 0 if __name__ == "__main__": raise SystemExit(main())
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now