Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.

All Dogs go to Heaven 2 Animated Moviebook (PC) .IWX file

Featured Replies

  • Localization
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())

Create an account or sign in to comment

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.