Jump to content

Leisure Suit Larry: Magna Cum Laude .JAM Files


Angel333119

Recommended Posts

Hello, could someone help me understand the structure of the file for documentation purposes?

dNDD0I2.png

 

4 - magic (JAM2)
4 - Unknown
4 - offset of the first file data in the container
4 - magic 2 (none)
12 - null
2 - Number Of Filenames
2 - Number Of Extensions

// for each filename
8 - Filename

// for each extension
4 - Extension Name // the first extension is null

8 - Unknown
4 - offset of the first file size
12 - Unknown
4 - offset of the first ID

// for each file
2 - Filename ID
2 - File Extension ID
4 - Offset

//for each file
4 - Compressed File Size
4 - Decompressed File Size
24 - Unknown
XX - File Data
1 - FF
*next file

INTRFRAM.rar

Link to comment
Share on other sites

Here's the file format from xentax wiki backup:
 


char {4}     - Header (JAM2)
uint32 {4}   - Unknown
uint32 {4}   - First File Offset
char {4}     - Header 2 (none)
byte {12}    - null
uint16 {2}   - Number Of Filenames
uint16 {2}   - Number Of Extensions

// for each filename

    char {8}   - Filename (null)


// for each extension

    char {4}   - Extension Name (null) // the first extension is all nulls


uint32 {4}   - Unknown

// NOTE: Some files have invalid offsets - only allow offsets >= FirstFileOffset
// for each file

    uint16 {2}   - Filename ID
    uint16 {2}   - File Extension ID
    uint32 {4}   - Offset


// File Data

    // for each file

        uint32 {4}   - Compressed File Size
        uint32 {4}   - Decompressed File Size
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        byte {X}     - File Data
        byte {0-3}   - Junk padding to a multiple of 4 bytes

 

Link to comment
Share on other sites

1 hour ago, ikskoks said:

Here's the file format from xentax wiki backup:
 


char {4}     - Header (JAM2)
uint32 {4}   - Unknown
uint32 {4}   - First File Offset
char {4}     - Header 2 (none)
byte {12}    - null
uint16 {2}   - Number Of Filenames
uint16 {2}   - Number Of Extensions

// for each filename

    char {8}   - Filename (null)


// for each extension

    char {4}   - Extension Name (null) // the first extension is all nulls


uint32 {4}   - Unknown

// NOTE: Some files have invalid offsets - only allow offsets >= FirstFileOffset
// for each file

    uint16 {2}   - Filename ID
    uint16 {2}   - File Extension ID
    uint32 {4}   - Offset


// File Data

    // for each file

        uint32 {4}   - Compressed File Size
        uint32 {4}   - Decompressed File Size
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        uint32 {4}   - Unknown
        byte {X}     - File Data
        byte {0-3}   - Junk padding to a multiple of 4 bytes

 


This is exactly the same thing I published, but incomplete.

I need someone to help me understand the file in its entirety.

I found this schema that you say is from Xentax, and it's even on Zenhax at this link:

https://zenhax.com/viewtopic.php@t=17851.html

However, this didn’t help me much, as it’s incorrect in several points that have been corrected in my documentation.

Edited by Angel333119
Link to comment
Share on other sites

Some files have incorrect offset, so i am ignoring that.

here the code to extract your file.

 

import os
import struct
import tkinter as tk
from tkinter import filedialog

def read_string(file, length):
    return file.read(length).decode('utf-8').strip('\x00')

def extract_files(file_path, output_folder):
    with open(file_path, 'rb') as f:
        # Ler o cabeçalho
        magic = f.read(4)
        unknown = f.read(4)
        first_file_offset = struct.unpack('<I', f.read(4))[0]
        magic2 = f.read(4)
        null_bytes = f.read(12)
        num_filenames = struct.unpack('<H', f.read(2))[0]
        num_extensions = struct.unpack('<H', f.read(2))[0]

        # Ler os nomes dos arquivos
        filenames = []
        for _ in range(num_filenames):
            filenames.append(read_string(f, 8))

        # Ler as extensões dos arquivos
        extensions = []
        for _ in range(num_extensions):
            extensions.append(read_string(f, 4))

        # Ler informações adicionais
        unknown = f.read(8)
        offset_first_file_size = struct.unpack('<I', f.read(4))[0]
        unknown = f.read(12)
        offset_first_id = struct.unpack('<I', f.read(4))[0]

        # Ler os arquivos
        files = []
        for i in range(num_filenames):
            filename_id = struct.unpack('<H', f.read(2))[0]
            file_extension_id = struct.unpack('<H', f.read(2))[0]
            offset = struct.unpack('<I', f.read(4))[0]
            files.append((i, filename_id, file_extension_id, offset))

        # Ler os tamanhos e dados dos arquivos
        for position, filename_id, file_extension_id, offset in files:
            if offset < first_file_offset:
                print(f"Aviso: Offset inválido ({offset}) para o arquivo na posição {position}. Ignorando este arquivo.")
                continue

            f.seek(offset)
            compressed_size = struct.unpack('<I', f.read(4))[0]
            decompressed_size = struct.unpack('<I', f.read(4))[0]
            unknown = f.read(24)
            file_data = f.read(compressed_size)
            f.read(1)  # FF

            # Salvar o arquivo extraído
            filename = filenames[filename_id % num_filenames]
            extension = extensions[file_extension_id % num_extensions]
            output_path = os.path.join(output_folder, f"{filename}.{extension}")
            with open(output_path, 'wb') as out_file:
                out_file.write(file_data)

            print(f"Extraído: {filename}.{extension}")
            print(f"  Posição: {position}")
            print(f"  Offset: {offset}")
            print(f"  Tamanho comprimido: {compressed_size}")
            print(f"  Tamanho descomprimido: {decompressed_size}")
            print(f"  ID do nome do arquivo: {filename_id}")
            print(f"  ID da extensão: {file_extension_id}")
            print("--------------------")

if __name__ == "__main__":
    # Criar uma janela root (mas não a mostraremos)
    root = tk.Tk()
    root.withdraw()

    # Abrir o diálogo de seleção de arquivo
    file_path = filedialog.askopenfilename(
        title="Selecione o arquivo JAM",
        filetypes=[("Arquivos JAM", "*.JAM"), ("Todos os arquivos", "*.*")]
    )

    if file_path:
        # O usuário selecionou um arquivo
        output_folder = "extraidos"
        os.makedirs(output_folder, exist_ok=True)
        print(f"Extraindo arquivos de: {file_path}")
        extract_files(file_path, output_folder)
        print(f"Extração concluída. Os arquivos foram salvos em: {os.path.abspath(output_folder)}")
    else:
        print("Nenhum arquivo selecionado. Encerrando o programa.")

    # Fechar a janela root
    root.destroy()

 

leisure _unpack.py

Link to comment
Share on other sites

The problem is reconstructing the file. The truth is that I want to understand the file so I can extract and rebuild it, both by adding new files and using only the original ones. Some of the unknown bytes have some relevance because the same file on PC and PS2 has different values. Some others with similar values are probably just junk.

Edited by Angel333119
Link to comment
Share on other sites

Hi, I know this is probably useless, but the second 4 Byte is most probably a UNIX Hex timestamp.

4 - magic (JAM2)
4 - hexadecimal timestamp when packed
4 - offset of the first file data in the container
4 - magic 2 (none)

Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...