Angel333119 Posted September 1 Share Posted September 1 Hello, could someone help me understand the structure of the file for documentation purposes? 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 More sharing options...
ikskoks Posted September 1 Share Posted September 1 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 More sharing options...
Angel333119 Posted September 1 Author Share Posted September 1 (edited) 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 September 1 by Angel333119 Link to comment Share on other sites More sharing options...
Rabatini Posted September 1 Share Posted September 1 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 More sharing options...
Angel333119 Posted September 2 Author Share Posted September 2 (edited) 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 September 2 by Angel333119 Link to comment Share on other sites More sharing options...
Rabatini Posted September 2 Share Posted September 2 (edited) Try this, if works in the game, need make some ajustments, but maybe the game accepts OLD_leisure_pack.py OLD_leisure_pack.py Edited September 3 by Rabatini Link to comment Share on other sites More sharing options...
Anti6 Posted 3 hours ago Share Posted 3 hours ago 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 More sharing options...
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