seregonwar/PFU-PupFileUnpacker

pup_module.py Bug Report

Closed this issue · 0 comments

Here are the bugs I found in the code:

  1. In the Pup class constructor, the magic, version, mode, entry_table_offset, and entry_table_count parameters should not have default values. These values are extracted from the file and should be passed as arguments when creating a Pup object.
  2. In the extract_pup_file function, the file_path variable is assigned the result of the select_file function, but the function call is missing parentheses. It should be file_path = select_file().
  3. In the Pup class constructor, the entry_table attribute is initialized as an empty list, but it is never populated with actual entry data. This will cause an error when trying to iterate over pup.entry_table later in the code.
  4. In the extract_pup_file function, the entry_table_size variable is extracted from the file buffer, but it is not used to calculate the number of entries in the entry table. Instead, the number of entries is calculated as entry_table_size // 24, which assumes that each entry is exactly 24 bytes long. This may not be true, so it's better to use struct.unpack to extract the actual number of entries from the buffer.
  5. In the extract_pup_file function, the entry_data variable is assigned the result of decompressing the entry data using the lzma.decompress function. However, if the entry is not compressed (i.e., entry_compression is False), this will cause an error. Instead, entry_data should be assigned buffer[entry_data_offset:entry_data_offset+entry_data_size] in this case.
  6. The extract_pup_file function is called unconditionally at the end of the script, even if the user does not select a file. This will cause an error. Instead, the function call should be inside a if __name__ == '__main__': block to ensure that it is only called when the script is run directly.

Here's the corrected code:

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

class Pup:
    MAGIC = b'MYPUP123'

    def __init__(self, file_path, magic, version, mode, entry_table_offset, entry_table_count, entry_table):
        self.file_path = file_path
        self.magic = magic
        self.version = version
        self.mode = mode
        self.entry_table_offset = entry_table_offset
        self.entry_table_count = entry_table_count
        self.entry_table = entry_table

def select_file():
    root = tk.Tk()
    root.withdraw()
    return filedialog.askopenfilename(filetypes=[('PUP files', '*.pup')])

def extract_pup_file():
    file_path = select_file()

    # Extract information from the file buffer
    with open(file_path, 'rb') as f:
        buffer = f.read()

    padding_len = len(buffer) % 16
    if padding_len != 0:
        raise ValueError(f"The file {os.path.basename(file_path)} has incorrect padding.")

    magic = buffer[:8]
    version = buffer[8:12]
    mode = buffer[12:16]
    entry_table_offset = struct.unpack("<Q", buffer[32:40])[0]
    entry_table_count = struct.unpack("<I", buffer[48:52])[0]

    if magic != Pup.MAGIC:
        raise ValueError(f"The file {os.path.basename(file_path)} has an incorrect magic number.")

    # Extract the entry table
    entry_table = []
    for i in range(entry_table_count):
        offset = entry_table_offset + i * 24
        entry = struct.unpack("<6sIHQQI", buffer[offset:offset+24])
        entry_table.append(entry)

    # Create a Pup object with the extracted information
    pup = Pup(file_path, magic, version, mode, entry_table_offset, entry_table_count, entry_table)

    # Extract the entry data
    dir_path = os.path.dirname(file_path)
    pup_name = os.path.basename(file_path)[:-4]
    pup_dir_path = os.path.join(dir_path, pup_name)
    if not os.path.exists(pup_dir_path):
        os.makedirs(pup_dir_path)

    for entry in pup.entry_table:
        entry_type = entry[0]
        entry_flags = entry[1]
        entry_compression = entry[2]
        entry_uncompressed_size = entry[3]
        entry_compressed_size = entry[4]
        entry_hash = entry[5]
        entry_data_offset = entry[6]
        entry_data_size = entry_compressed_size if entry_compression else entry_uncompressed_size
        entry_data_offset = entry_data_offset + len(buffer) if entry_data_offset > 0 else entry_data_offset

        if entry_compression:
            entry_data = lzma.decompress(buffer[entry_data_offset:entry_data_offset+entry_compressed_size])
        else:
            entry_data = buffer[entry_data_offset:entry_data_offset+entry_data_size]

        # Write the entry data to a file
        file_name = f"{i:06d}.bin"
        file_path = os.path.join(pup_dir_path, file_name)

        with open(file_path, 'wb') as f:
            f.write(entry_data)

    messagebox.showinfo("Information", "Extraction completed successfully.")

if __name__ == '__main__':
    extract_pup_file()