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.

[Xbox 360] Tom Clancy's Ghost Recon Advanced Warfighter 2 Yeti.big / .glb

Featured Replies

  • Localization

Does anyone have an idea to write a tutorial to restore filenames from Yeti.big and unpack data from GLBs (not to be confused with the open standard 3D model file format by Khronos Group)? The GLBs have data compression/encryption/obfuscation. I tried all Aluigi's QuickBMS script but they failed.

Yeti.big: https://mega.nz/file/sSRkFTCY#9N-2CMFeRyzTDshGhqnBZf6tpq4BrifLchIG2L7u7fU

.glb: https://mega.nz/file/NaRjQIpD#Mvs83RV9HtZMiYzHQn0-TSMwsn-0_dcRGOxu2pM_GVQ

Here are the Xbox 360 executables to analyze if you want to log filenames from Yeti.big and decrypt GLBs

 

graw2xbox360executables.rar

  • Localization

Regarding the glb files, I managed to figure out their structure:

Header: 
4 bytes, version/ID
4 bytes file count


Name index: 
4 bytes name size
name
4 bytes compressed_size
4 bytes decompressed_size
4 bytes compression_flag ?
4 bytes offset   - data adress


ect.

RAW DATA

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



Unfortunately, I was unable to decompress them.


Here's the code to unpack -- no decompression

import os
import struct
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext


def read_u32(f):
    data = f.read(4)
    if len(data) != 4:
        raise EOFError("Unexpected end of file while reading uint32.")
    return struct.unpack("<I", data)[0]


def safe_decode_name(raw):
    for enc in ("utf-8", "cp1250", "latin-1"):
        try:
            return raw.decode(enc).strip("\x00")
        except Exception:
            pass
    return raw.hex()


def sanitize_filename(name):
    bad = '<>:"/\\|?*'
    for ch in bad:
        name = name.replace(ch, "_")
    name = name.strip()
    return name or "unnamed_file"


def extract_glb_archive(input_path, output_dir, log):
    with open(input_path, "rb") as f:
        archive_size = os.path.getsize(input_path)

        version_id = f.read(4)
        if len(version_id) != 4:
            raise Exception("File is too small or corrupted.")

        file_count = read_u32(f)

        log("")
        log(f"Archive ID / Version : {version_id.hex(' ').upper()}")
        log(f"Total Files          : {file_count}")
        
        entries = []

        for i in range(file_count):
            name_size = read_u32(f)

            if name_size <= 0 or name_size > 4096:
                raise Exception(f"Suspicious name size at entry {i}: {name_size}")

            name_raw = f.read(name_size)
            if len(name_raw) != name_size:
                raise Exception(f"Failed to read file name at entry {i}")

            name = safe_decode_name(name_raw)

            compressed_size = read_u32(f)
            decompressed_size = read_u32(f)
            compression_flag = read_u32(f)
            data_offset = read_u32(f)

            entries.append({
                "name": name,
                "compressed_size": compressed_size,
                "decompressed_size": decompressed_size,
                "compression_flag": compression_flag,
                "offset": data_offset,
            })

        extracted_count = 0
        skipped_count = 0
        total_compressed = 0
        total_decompressed = 0

        for i, entry in enumerate(entries):
            name = sanitize_filename(entry["name"])
            compressed_size = entry["compressed_size"]
            decompressed_size = entry["decompressed_size"]
            compression_flag = entry["compression_flag"]
            offset = entry["offset"]

            total_compressed += compressed_size
            total_decompressed += decompressed_size

            if offset >= archive_size:
                log(f"[SKIP] {name}: offset is outside archive.")
                skipped_count += 1
                continue

            if offset + compressed_size > archive_size:
                log(f"[WARN] {name}: compressed size exceeds archive bounds. Truncating.")
                compressed_size = archive_size - offset

            out_path = os.path.join(output_dir, name)
            os.makedirs(os.path.dirname(out_path), exist_ok=True)

            f.seek(offset)
            data = f.read(compressed_size)

            with open(out_path, "wb") as out:
                out.write(data)

            extracted_count += 1

            log(
                f"{name} | "
                f"Extracted: {len(data):,} bytes | "
                f"Decompressed Size: {decompressed_size:,} bytes | "
                f"Flag: 0x{compression_flag:08X} | "
                f"Offset: 0x{offset:08X}"
            )

        log("")
        log("Extraction completed successfully.")


class ExtractorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("GLB Archive Extractor")
        self.root.geometry("950x620")
        self.root.resizable(False, False)

        self.input_path = tk.StringVar()
        self.output_dir = tk.StringVar()

        tk.Label(root, text="GLB Archive File:", font=("Arial", 10, "bold")).pack(anchor="w", padx=10, pady=(10, 2))

        frame1 = tk.Frame(root)
        frame1.pack(fill="x", padx=10)

        tk.Entry(frame1, textvariable=self.input_path).pack(side="left", fill="x", expand=True)
        tk.Button(frame1, text="Browse", command=self.select_input).pack(side="left", padx=5)

        tk.Label(root, text="Output Folder:", font=("Arial", 10, "bold")).pack(anchor="w", padx=10, pady=(10, 2))

        frame2 = tk.Frame(root)
        frame2.pack(fill="x", padx=10)

        tk.Entry(frame2, textvariable=self.output_dir).pack(side="left", fill="x", expand=True)
        tk.Button(frame2, text="Browse", command=self.select_output).pack(side="left", padx=5)

        tk.Button(
            root,
            text="Extract Files",
            command=self.extract,
            bg="#2e7d32",
            fg="white",
            font=("Arial", 12, "bold"),
            height=2
        ).pack(fill="x", padx=10, pady=15)

        self.log_box = scrolledtext.ScrolledText(root, font=("Consolas", 9))
        self.log_box.pack(fill="both", expand=True, padx=10, pady=(0, 10))

    def log(self, text):
        self.log_box.insert(tk.END, text + "\n")
        self.log_box.see(tk.END)
        self.root.update_idletasks()

    def select_input(self):
        path = filedialog.askopenfilename(
            title="Select GLB archive file",
            filetypes=[
                ("GLB files", "*.glb"),
                ("All files", "*.*")
            ]
        )
        if path:
            self.input_path.set(path)

    def select_output(self):
        path = filedialog.askdirectory(title="Select output folder")
        if path:
            self.output_dir.set(path)

    def extract(self):
        input_path = self.input_path.get().strip()
        output_dir = self.output_dir.get().strip()

        if not input_path:
            messagebox.showerror("Error", "No GLB archive file selected.")
            return

        if not output_dir:
            messagebox.showerror("Error", "No output folder selected.")
            return

        try:
            self.log_box.delete("1.0", tk.END)
            extract_glb_archive(input_path, output_dir, self.log)
            messagebox.showinfo("Done", "Extraction completed.")
        except Exception as e:
            self.log(f"[ERROR] {e}")
            messagebox.showerror("Error", str(e))


if __name__ == "__main__":
    root = tk.Tk()
    app = ExtractorGUI(root)
    root.mainloop()

 

  • Localization

the .glb definitely use some compression algo with a huffman tree with the tree being at the beginning of the data, it's a list of u16 terminated by "00 00"

This is at least true for all the compressed XPR2 files I've looked at

Scratch all that, it's BPE

Edited by NeoGT404

  • Localization

Here's my current decompressor that's able to decompress shorter files but not longer ones:

EXPORT ssize_t decompress_bpe(const uint8_t *restrict src, const size_t zsize,
                                    uint8_t *restrict dst, const ssize_t usize) {
    if (usize == 0) return 0;
    if (zsize < 2) return -1;
    uint8_t ls[0x100];
    uint8_t rs[0x100];
    uint16_t pc = 0;
    size_t ip = 0;

    while (ip < zsize) {
        uint8_t l = src[ip++];
        if (!l) break;

        if (ip >= zsize) return -1;
        uint8_t r = src[ip++];
        ls[pc] = l;
        rs[pc++] = r;
        if (pc >= 0x100) break;
    }

    uint16_t tokb = 0x100 - pc;
    ssize_t op = 0;
    uint8_t stack[0x200];
    uint16_t sp = 0;

    while ((ip < zsize || sp > 0) && (op < usize || usize == -1)) {
        uint8_t v;
        if (sp > 0) v = stack[--sp];
        else v = src[ip++];

        if (v >= tokb) {
            if (sp + 2 > 0x200) return -1;
            stack[sp++] = rs[v - tokb];
            stack[sp++] = ls[v - tokb];
        } else dst[op++] = v;
    }

    return op;
}

 

  • Localization
1 hour ago, NeoGT404 said:

Here's my current decompressor that's able to decompress shorter files but not longer ones:

EXPORT ssize_t decompress_bpe(const uint8_t *restrict src, const size_t zsize,
                                    uint8_t *restrict dst, const ssize_t usize) {
    if (usize == 0) return 0;
    if (zsize < 2) return -1;
    uint8_t ls[0x100];
    uint8_t rs[0x100];
    uint16_t pc = 0;
    size_t ip = 0;

    while (ip < zsize) {
        uint8_t l = src[ip++];
        if (!l) break;

        if (ip >= zsize) return -1;
        uint8_t r = src[ip++];
        ls[pc] = l;
        rs[pc++] = r;
        if (pc >= 0x100) break;
    }

    uint16_t tokb = 0x100 - pc;
    ssize_t op = 0;
    uint8_t stack[0x200];
    uint16_t sp = 0;

    while ((ip < zsize || sp > 0) && (op < usize || usize == -1)) {
        uint8_t v;
        if (sp > 0) v = stack[--sp];
        else v = src[ip++];

        if (v >= tokb) {
            if (sp + 2 > 0x200) return -1;
            stack[sp++] = rs[v - tokb];
            stack[sp++] = ls[v - tokb];
        } else dst[op++] = v;
    }

    return op;
}

 

I applied your code, and apart from the correct decompression sizes, nothing else matches. The data is completely corrupted. At first, there are several different bytes, and then there's a constant repetition of one byte.

  • Author
  • Localization
On 5/22/2026 at 7:04 PM, NeoGT404 said:

Here's my current decompressor that's able to decompress shorter files but not longer ones:

EXPORT ssize_t decompress_bpe(const uint8_t *restrict src, const size_t zsize,
                                    uint8_t *restrict dst, const ssize_t usize) {
    if (usize == 0) return 0;
    if (zsize < 2) return -1;
    uint8_t ls[0x100];
    uint8_t rs[0x100];
    uint16_t pc = 0;
    size_t ip = 0;

    while (ip < zsize) {
        uint8_t l = src[ip++];
        if (!l) break;

        if (ip >= zsize) return -1;
        uint8_t r = src[ip++];
        ls[pc] = l;
        rs[pc++] = r;
        if (pc >= 0x100) break;
    }

    uint16_t tokb = 0x100 - pc;
    ssize_t op = 0;
    uint8_t stack[0x200];
    uint16_t sp = 0;

    while ((ip < zsize || sp > 0) && (op < usize || usize == -1)) {
        uint8_t v;
        if (sp > 0) v = stack[--sp];
        else v = src[ip++];

        if (v >= tokb) {
            if (sp + 2 > 0x200) return -1;
            stack[sp++] = rs[v - tokb];
            stack[sp++] = ls[v - tokb];
        } else dst[op++] = v;
    }

    return op;
}

 

I'm looking for tools that unpack Yeti.big with filenames.

Edited by mrmaller1905

  • Localization
1 hour ago, zbirow said:

I applied your code, and apart from the correct decompression sizes, nothing else matches. The data is completely corrupted. At first, there are several different bytes, and then there's a constant repetition of one byte.

it works on some of the small (< ~1kb) xml like files (.mfx, .cbo) and I'm pretty sure there's some extra stuff going on because the first token thingy is seemingly garbage (at least to my current decompressor) but I think that might dictate the rest of the compression

Edited by NeoGT404

  • Localization
35 minutes ago, NeoGT404 said:

it works on some of the small (< ~1kb) xml like files (.mfx, .cbo) and I'm pretty sure there's some extra stuff going on because the first token thingy is seemingly garbage (at least to my current decompressor) but think that might dictate the rest of the compression

Notice that it always starts with FE 7F, so it could be some kind of Magic Header or settings.

  • Localization
3 hours ago, NeoGT404 said:

it works on some of the small (< ~1kb) xml like files (.mfx, .cbo) and I'm pretty sure there's some extra stuff going on because the first token thingy is seemingly garbage (at least to my current decompressor) but think that might dictate the rest of the compression



probable BPE structure, but I'm not 100% sure yet.

BPE dictionary:
pair_l, pair_r
pair_l, pair_r
pair_l, pair_r
...
00

chunk:
1 byte chunk_size
chunk_size bytes compressed stream

Edited by zbirow

  • Localization
13 hours ago, NeoGT404 said:

that could be it, then maybe a chunk size of 0 being a special command?

I analyzed several files and found a repeating structure

FE 7F start header
FE FF end header
3 unk bytes
00 - separator
size data
data


Although you should pay attention to this 1kb file, because there's only one file in it, and it only has FE 7F FE FF 00 31, so there's nothing after FE FF, because there's no compression there.



EDIT


Or, 00 is not a sprarator.

BPE table - x bytes
2 bytes flags/settings   \\\   00 00 / 40 00 
2 bytes big-endian compressed chunk size    \\\   00 31/ 00 6D
data


which is suitable for larger files, e.g. 40 00 01 29.
40 00 - these are the settings,
01 29 - this is the data block size.

Edited by zbirow

  • Localization

BPE Structure in C

 

#include <stdint.h>
#include <stddef.h>

typedef long ssize_t;

static uint16_t read_be16(const uint8_t *p) {
    return ((uint16_t)p[0] << 8) | p[1];
}

ssize_t decompress_graw_bpe(
    const uint8_t *restrict src,
    size_t zsize,
    uint8_t *restrict dst,
    ssize_t usize
) {
    size_t ip = 0;
    ssize_t op = 0;

    while (ip < zsize && (usize < 0 || op < usize)) {
        uint8_t left[0x100];
        uint8_t right[0x100];

        for (int i = 0; i < 0x100; i++) {
            left[i] = (uint8_t)i;
            right[i] = 0;
        }

        uint16_t code = 0;

        while (code < 0x100) {
            if (ip >= zsize) return -1;

            uint8_t count = src[ip++];

            if (count > 0x7F) {
                code += count - 0x7F;
                count = 0;
            }

            for (uint16_t i = 0; i <= count && code < 0x100; i++, code++) {
                if (ip >= zsize) return -1;

                uint8_t l = src[ip++];
                left[code] = l;

                if (l != code) {
                    if (ip >= zsize) return -1;
                    right[code] = src[ip++];
                }
            }
        }

        if (ip + 2 > zsize) return -1;

        uint16_t packed_size = read_be16(src + ip);
        ip += 2;

        if (ip + packed_size > zsize) return -1;

        size_t block_end = ip + packed_size;

        while (ip < block_end) {
            uint8_t stack[0x1000];
            uint16_t sp = 0;

            stack[sp++] = src[ip++];

            while (sp > 0) {
                uint8_t v = stack[--sp];

                if (left[v] == v) {
                    if (usize >= 0 && op >= usize) return -1;
                    dst[op++] = v;
                } else {
                    if (sp + 2 > sizeof(stack)) return -1;

                    stack[sp++] = right[v];
                    stack[sp++] = left[v];
                }
            }
        }
    }

    if (usize >= 0 && op != usize) return -1;

    return op;
}




Python unpack code:

 

import argparse
import os
import struct
import sys
import threading
import queue


def read_u32(f):
    data = f.read(4)
    if len(data) != 4:
        raise EOFError("Unexpected end of file while reading uint32.")
    return struct.unpack("<I", data)[0]


def safe_decode_name(raw):
    for enc in ("utf-8", "cp1250", "latin-1"):
        try:
            return raw.decode(enc).strip("\x00")
        except Exception:
            pass
    return raw.hex()


def sanitize_filename(name):
    bad = '<>:"/\\|?*'
    for ch in bad:
        name = name.replace(ch, "_")
    name = name.strip()
    return name or "unnamed_file"


def parse_entries(input_path):
    entries = []
    with open(input_path, "rb") as f:
        version_id = f.read(4)
        if len(version_id) != 4:
            raise ValueError("File is too small or corrupted.")

        file_count = read_u32(f)

        for i in range(file_count):
            name_size = read_u32(f)
            if name_size <= 0 or name_size > 4096:
                raise ValueError(f"Suspicious name size at entry {i}: {name_size}")

            name_raw = f.read(name_size)
            if len(name_raw) != name_size:
                raise ValueError(f"Failed to read file name at entry {i}")

            entries.append(
                {
                    "index": i,
                    "name": safe_decode_name(name_raw),
                    "compressed_size": read_u32(f),
                    "decompressed_size": read_u32(f),
                    "compression_flag": read_u32(f),
                    "offset": read_u32(f),
                }
            )

    return version_id, entries


def decode_bpe_to_file(data, out, expected_size=None):
    """Decode Philip Gage style block BPE used by these GLB entries."""
    pos = 0
    decoded_size = 0
    block_count = 0
    write_buffer = bytearray()

    def emit(value):
        nonlocal decoded_size, write_buffer
        write_buffer.append(value)
        decoded_size += 1
        if len(write_buffer) >= 65536:
            out.write(write_buffer)
            write_buffer.clear()

    while pos < len(data):
        left = list(range(256))
        right = [0] * 256
        code = 0

        if pos >= len(data):
            break

        count = data[pos]
        pos += 1

        while True:
            if count > 127:
                code += count - 127
                count = 0

            if code == 256:
                break

            for _ in range(count + 1):
                if pos >= len(data):
                    raise EOFError("Truncated BPE pair table.")

                left_value = data[pos]
                pos += 1
                left[code] = left_value

                if code != left_value:
                    if pos >= len(data):
                        raise EOFError("Truncated BPE pair table right value.")
                    right[code] = data[pos]
                    pos += 1

                code += 1
                if code == 256:
                    break

            if code == 256:
                break

            if pos >= len(data):
                raise EOFError("Truncated BPE table control byte.")
            count = data[pos]
            pos += 1

        if pos + 2 > len(data):
            raise EOFError("Truncated BPE block size.")

        packed_size = (data[pos] << 8) | data[pos + 1]
        pos += 2

        if pos + packed_size > len(data):
            raise EOFError(
                f"Truncated BPE block data: need {packed_size}, have {len(data) - pos}."
            )

        end = pos + packed_size
        stack = []

        while pos < end:
            stack.append(data[pos])
            pos += 1

            while stack:
                value = stack.pop()
                if value == left[value]:
                    emit(value)
                else:
                    stack.append(right[value])
                    stack.append(left[value])

        block_count += 1

    if write_buffer:
        out.write(write_buffer)

    if expected_size is not None and decoded_size != expected_size:
        raise ValueError(
            f"Decoded size mismatch: got {decoded_size}, expected {expected_size}."
        )

    return decoded_size, block_count


def extract_glb_archive(input_path, output_dir, raw=False, log=print):
    archive_size = os.path.getsize(input_path)
    version_id, entries = parse_entries(input_path)

    log(f"Archive              : {input_path}")
    log(f"Archive ID / Version : {version_id.hex(' ').upper()}")
    log(f"Total Files          : {len(entries)}")

    extracted_count = 0
    skipped_count = 0

    with open(input_path, "rb") as f:
        for entry in entries:
            name = sanitize_filename(entry["name"])
            compressed_size = entry["compressed_size"]
            decompressed_size = entry["decompressed_size"]
            compression_flag = entry["compression_flag"]
            offset = entry["offset"]

            if offset >= archive_size:
                log(f"[SKIP] {name}: offset is outside archive.")
                skipped_count += 1
                continue

            if offset + compressed_size > archive_size:
                raise ValueError(f"{name}: compressed size exceeds archive bounds.")

            out_path = os.path.join(output_dir, name)
            os.makedirs(os.path.dirname(out_path), exist_ok=True)

            f.seek(offset)
            data = f.read(compressed_size)

            with open(out_path, "wb") as out:
                if raw or compression_flag == 0:
                    out.write(data)
                    written_size = len(data)
                    block_count = 0
                else:
                    written_size, block_count = decode_bpe_to_file(
                        data, out, decompressed_size
                    )

            extracted_count += 1
            log(
                f"{entry['index']:04d} {name} | "
                f"written={written_size:,} | "
                f"packed={compressed_size:,} | "
                f"expected={decompressed_size:,} | "
                f"flag=0x{compression_flag:08X} | "
                f"blocks={block_count}"
            )

    log("")
    log(f"Done. Extracted={extracted_count}, skipped={skipped_count}")


def default_output_dir(input_path):
    parent = os.path.dirname(os.path.abspath(input_path))
    name = os.path.splitext(os.path.basename(input_path))[0]
    return os.path.join(parent, name)


def launch_gui():
    import tkinter as tk
    from tkinter import filedialog, messagebox, scrolledtext

    class ExtractorGUI:
        def __init__(self, root):
            self.root = root
            self.root.title("GRAW 2 GLB Extractor")
            self.root.geometry("980x640")
            self.root.minsize(780, 520)

            self.input_path = tk.StringVar()
            self.output_dir = tk.StringVar()
            self.events = queue.Queue()
            self.worker = None

            tk.Label(root, text="GLB archive:", font=("Arial", 10, "bold")).pack(
                anchor="w", padx=10, pady=(10, 2)
            )

            file_frame = tk.Frame(root)
            file_frame.pack(fill="x", padx=10)

            tk.Entry(file_frame, textvariable=self.input_path).pack(
                side="left", fill="x", expand=True
            )
            tk.Button(file_frame, text="Browse", command=self.select_input).pack(
                side="left", padx=(5, 0)
            )

            tk.Label(root, text="Output folder:", font=("Arial", 10, "bold")).pack(
                anchor="w", padx=10, pady=(10, 2)
            )

            output_frame = tk.Frame(root)
            output_frame.pack(fill="x", padx=10)

            tk.Entry(output_frame, textvariable=self.output_dir, state="readonly").pack(
                side="left", fill="x", expand=True
            )

            self.extract_button = tk.Button(
                root,
                text="Extract Files",
                command=self.extract,
                bg="#2e7d32",
                fg="white",
                font=("Arial", 12, "bold"),
                height=2,
            )
            self.extract_button.pack(fill="x", padx=10, pady=15)

            self.log_box = scrolledtext.ScrolledText(root, font=("Consolas", 9))
            self.log_box.pack(fill="both", expand=True, padx=10, pady=(0, 10))

            self.root.after(100, self.poll_events)

        def select_input(self):
            path = filedialog.askopenfilename(
                title="Select GLB archive",
                filetypes=[("GLB archives", "*.glb"), ("All files", "*.*")],
            )
            if not path:
                return

            self.input_path.set(path)
            self.output_dir.set(default_output_dir(path))

        def log(self, text):
            self.log_box.insert(tk.END, text + "\n")
            self.log_box.see(tk.END)

        def set_running(self, running):
            self.extract_button.config(
                state=("disabled" if running else "normal"),
                text=("Extracting..." if running else "Extract Files"),
            )

        def extract(self):
            input_path = self.input_path.get().strip()
            if not input_path:
                messagebox.showerror("Error", "No GLB archive selected.")
                return

            output_dir = default_output_dir(input_path)
            self.output_dir.set(output_dir)

            self.log_box.delete("1.0", tk.END)
            self.log(f"Output folder: {output_dir}")
            self.log("")
            self.set_running(True)

            self.worker = threading.Thread(
                target=self.run_extract, args=(input_path, output_dir), daemon=True
            )
            self.worker.start()

        def run_extract(self, input_path, output_dir):
            try:
                extract_glb_archive(
                    input_path,
                    output_dir,
                    log=lambda text: self.events.put(("log", text)),
                )
                self.events.put(("done", output_dir))
            except Exception as exc:
                self.events.put(("error", str(exc)))

        def poll_events(self):
            try:
                while True:
                    event, value = self.events.get_nowait()
                    if event == "log":
                        self.log(value)
                    elif event == "done":
                        self.set_running(False)
                        self.log("")
                        self.log(f"Finished. Files are in: {value}")
                        messagebox.showinfo("Done", f"Extraction completed:\n{value}")
                    elif event == "error":
                        self.set_running(False)
                        self.log(f"[ERROR] {value}")
                        messagebox.showerror("Error", value)
            except queue.Empty:
                pass

            self.root.after(100, self.poll_events)

    root = tk.Tk()
    ExtractorGUI(root)
    root.mainloop()


def main():
    if len(sys.argv) == 1:
        launch_gui()
        return

    parser = argparse.ArgumentParser(
        description="Extract and BPE-decompress GRAW Xbox 360 GLB archives."
    )
    parser.add_argument("archives", nargs="*", help="Input .glb archive(s).")
    parser.add_argument(
        "-o",
        "--output",
        default="_decoded_glb",
        help="Output directory. Defaults to _decoded_glb.",
    )
    parser.add_argument(
        "--raw",
        action="store_true",
        help="Write packed entry data without BPE decompression.",
    )
    parser.add_argument(
        "--gui",
        action="store_true",
        help="Launch the graphical interface.",
    )
    args = parser.parse_args()

    if args.gui:
        launch_gui()
        return

    if not args.archives:
        parser.error("No archives supplied. Use --gui or pass one or more .glb files.")

    multiple = len(args.archives) > 1
    for archive in args.archives:
        base_output = args.output
        if multiple:
            base_output = os.path.join(
                args.output, os.path.splitext(os.path.basename(archive))[0]
            )
        extract_glb_archive(archive, base_output, raw=args.raw)


if __name__ == "__main__":
    main()



 

Edited by zbirow

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.