Jump to content

[PSP] Jikan de Fantasia (Japan) Extract files from RESARC.JRZ


Recommended Posts

Posted (edited)

I tried to extract the files from the file but I could not because of my little experience. I hope someone can help me extract the files. I will be happy to help you.

RESARC.rar

Edited by imranUSK
  • imranUSK changed the title to [PSP] Jikan de Fantasia (Japan) Extract files from RESARC.JRZ
Posted
5 hours ago, imranUSK said:

I tried to extract the files from the file but I could not because of my little experience. I hope someone can help me extract the files. I will be happy to help you.

RESARC.rar 1.71 MB · 9 downloads

The file you provide is composed of two parts
Index + file data
The index stores the offset and length of all files and their original names
I originally wanted to do a thorough parse through the index part to ensure the complete file extraction, but I encountered a problem when parsing the index
1. There are several file blocks that will have a few more bytes than other blocks
The content that caused the exception to be read
2. After jumping to the file location through the offset, there is a portion of the extra data, and these extra data are also different. Some have eight bytes of extra data before compressing the data, some have only four, and some will have more.
The file data is ordinary zlib compression, and the entire file should be compressed in this way
There is no way, I can only switch ideas and use a brute force extraction method
If you want to attach the recovery file name, you may need to thoroughly parse the structure of the index part to ensure that there are no strange phenomena.

import os
import zlib
import struct
import sys

def find_magic_bytes(file_handle, start_pos=0, magic=b'\x78\xDA'):
    file_handle.seek(start_pos)
    data = file_handle.read()
    return data.find(magic)

def process_file(input_file, output_dir):
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    with open(input_file, 'rb') as f:
        current_pos = 0
        file_size = os.path.getsize(input_file)

        while current_pos < file_size:
            magic_pos = find_magic_bytes(f, current_pos)
            if magic_pos == -1:
                break

            actual_pos = current_pos + magic_pos
            print(f"\nFound 78DA at position: {actual_pos} (0x{actual_pos:X})")

            f.seek(actual_pos - 4)
            length_bytes = f.read(4)
            if len(length_bytes) != 4:
                print("Error: Could not read 4 bytes before 78DA")
                current_pos = actual_pos + 2
                continue

            length = struct.unpack('<I', length_bytes)[0]
            print(f"Compressed data length: {length} (0x{length:X})")

            f.seek(actual_pos)
            compressed_data = f.read(length)

            try:
                decompressed_data = zlib.decompress(compressed_data)
                output_filename = f"{actual_pos:X}.dat"
                output_path = os.path.join(output_dir, output_filename)

                with open(output_path, 'wb') as out_file:
                    out_file.write(decompressed_data)

                print(f"Success! Saved to {output_path}")
                current_pos = actual_pos + length

            except zlib.error as e:
                print(f"Decompression failed: {e}")
                current_pos = actual_pos + 2

        print("\nFinished processing file.")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python jrz.py <input_file> <output_dir>")
        sys.exit(1)
    
    input_file = sys.argv[1]
    output_dir = sys.argv[2]
    process_file(input_file, output_dir)

 

  • Engineers
Posted (edited)

Basically you can treat the whole archive as a big blob, look for every zlib header (0x78 0xDA), and assume each one marks the start of a compressed file. Before the first header, you scan that “index” region with a simple regex to pull out any names like FOO.DAT or BAR.BIN. Then for each header you hand the rest of the data to zlib to decompress — if it works, you save it; if zlib chokes with error ‑3 (“incorrect header check”), we skip that spot. Whenever you have a matching filename from the index,you use it; otherwise you just name the file by its offset (e.g. 6FC9.bin). This brute‑force trick skips all the headaches of parsing every little variable-length entry in the index, but still recovers most of the original names.

 

import os
import sys
import re
import zlib

def find_magic_positions(data, magic=b'\x78\xDA'):
    """
    Return a list of offsets where the 2-byte zlib header appears.
    Note: brute‑force may find false positives inside compressed data.
    """
    return [i for i in range(len(data)-1) if data[i:i+2] == magic]

def parse_filenames(data, first_magic_pos):
    """
    Extract printable filenames from the index region (everything before
    the first compressed block).  Assumes names look like NAME.EXT.
    """
    idx_region = data[:first_magic_pos]
    # map non‑printable bytes to newlines so regex won't span them
    text = ''.join(chr(b) if 32 <= b < 127 else '\n' for b in idx_region)
    # simple regex for basename + dot + extension
    raw = re.findall(r'[A-Za-z0-9_]+\.[A-Za-z0-9]+', text)
    # dedupe while preserving order
    seen = set()
    names = []
    for nm in raw:
        if nm not in seen:
            seen.add(nm)
            names.append(nm)
    return names

def process_file(input_file, output_dir):
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    with open(input_file, 'rb') as f:
        data = f.read()

    magic_positions = find_magic_positions(data)
    if not magic_positions:
        print("No zlib blocks found.")
        return

    filenames = parse_filenames(data, magic_positions[0])
    print(f"Found {len(magic_positions)} compressed blocks.")
    print(f"Parsed {len(filenames)} filenames from index.\n")

    for idx, pos in enumerate(magic_positions):
        print(f"[{idx+1}/{len(magic_positions)}] at offset 0x{pos:X}")
        decomp = zlib.decompressobj()
        tail = data[pos:]
        try:
            out = decomp.decompress(tail)
            out += decomp.flush()
        except zlib.error as e:
            # e.args[0] often contains error code and message
            print(f"  Decompression failed (zlib error: {e.args[0]!r}). Skipping.")
            continue

        # choose parsed name or fallback
        if idx < len(filenames):
            name = filenames[idx]
        else:
            name = f"{pos:X}.bin"

        out_path = os.path.join(output_dir, name)
        with open(out_path, 'wb') as of:
            of.write(out)
        print(f"  Saved as: {name} ({len(out)} bytes)")

    print("\nDone.")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python jrz_extract.py <input_file> <output_dir>")
        sys.exit(1)
    process_file(sys.argv[1], sys.argv[2])

That should extract all files with "filename", and the rest that don't have it.

Edited by Rabatini
Posted

I've performed a resolution test on the index part. If there are no special cases in some parts, it should be normal to restore the file name

There is basically no problem with the first two indexes. The problem is in the third index, and there is a block that adds some extra data.

160686,1650824,495561,texture/acttex
87397,856280,495561,texture/character
18393,263222,495561,texture/item.bmp
6229,66614,513954,texture/npc.bmp
38673,263222,520183,texture/player.bmp
24102,263222,558856,texture/weapon.bmp
73289,794544,582958,texture/tutorial
2954,66614,582958,texture/op01_b.bmp
5133,196662,585912,texture/op02_b.bmp
9737,66614,591045,texture/op_chara.bmp
11285,66612,600782,texture/test0.bmp
7158,66612,612067,texture/test1.bmp
3988,65860,619225,texture/test2.bmp
7773,66612,623213,texture/test3.bmp
3109,65728,630986,texture/test4.bmp
9085,66614,634095,texture/test5.bmp
13067,66616,643180,texture/test6.bmp
511710,6997981,656247,texture/commontex
39217,289846,656247,texture/effect
20056,263222,656247,texture/effect.bmp
19161,26624,676303,texture/thumbs.db
43423,270215,695464,texture/font
41175,263222,695464,texture/font_line.bmp
2248,6993,736639,texture/ql.txt
217901,4580208,738887,texture/mapchips
3712,66614,738887,texture/gimic01.bmp
7214,263222,742599,texture/ground01.bmp
5080,262288,749813,texture/ground01_512.bmp
10406,263222,754893,texture/ground02.bmp
14198,263222,765299,texture/ground03.bmp
8984,263222,779497,texture/ground04.bmp
8385,263172,788481,texture/ground05.bmp
11110,263222,796866,texture/ground06.bmp
9368,263222,807976,texture/ground07.bmp
13627,263222,817344,texture/ground08.bmp
13539,263222,830971,texture/ground09.bmp
21793,263024,844510,texture/ground10.bmp
8794,262428,866303,texture/groundop.bmp
5293,263222,875097,texture/groundtown01.bmp
5435,262376,880390,texture/groundtown02.bmp
53,196,885825,texture/objectl.bmp
3536,66614,885878,texture/objectltexture01.bmp
3716,66614,889414,texture/objectltexture02.bmp
4620,66614,893130,texture/objectltexture03.bmp
3767,66614,897750,texture/objectltexture04.bmp
3797,66614,901517,texture/objectltexture05.bmp
5680,66614,905314,texture/objectltexture06.bmp
3484,66614,910994,texture/objectltexture07.bmp
5667,66614,914478,texture/objectltexture08.bmp
3877,66614,920145,texture/objectltexture09.bmp
5275,66614,924022,texture/objectltexture10.bmp
19115,31744,929297,texture/thumbs.db
5533,66614,948412,texture/towntexture01.bmp
2843,66614,953945,texture/towntexture02.bmp
195806,1836644,956788,texture/sys_cg
13068,66614,956788,texture/adhoc.bmp
5697,66614,969856,texture/e_button00.bmp
4996,66120,975553,texture/e_button01.bmp
5459,66120,980549,texture/e_button02.bmp
5364,66120,986008,texture/e_button03.bmp
5461,66120,991372,texture/e_button04.bmp
5668,66120,996833,texture/e_button05.bmp
5496,66120,1002501,texture/e_button06.bmp
5585,66120,1007997,texture/e_button07.bmp
5082,66120,1013582,texture/e_button08.bmp
5116,66614,1018664,texture/e_button09.bmp
6372,66116,1023780,texture/e_button10.bmp
4767,66614,1030152,texture/e_time00.bmp
4496,65848,1034919,texture/e_time01.bmp
4918,65848,1039415,texture/e_time02.bmp
4951,65848,1044333,texture/e_time03.bmp
4964,65848,1049284,texture/e_time04.bmp
4894,65848,1054248,texture/e_time05.bmp
4256,65848,1059142,texture/e_time06.bmp
4661,65848,1063398,texture/e_time07.bmp
5011,65848,1068059,texture/e_time08.bmp
4460,65848,1073070,texture/e_time09.bmp
5378,65848,1077530,texture/e_time10.bmp
14363,66614,1082908,texture/stage_select.bmp
10001,66614,1097271,texture/sys.bmp
4544,66614,1107272,texture/sys02.bmp
6561,66614,1111816,texture/sys03.bmp
34217,50176,1118377,texture/thumbs.db
15363,21068,1152594,texture/util
53,196,1152594,texture/daisi_yushagauge.bmp
53,196,1152647,texture/systemui_base.bmp
53,196,1152700,texture/systemui_base02.bmp
15204,20480,1152753,texture/thumbs.db
273337,1285628,1167957,texture/op_tex
103270,263224,1167957,texture/ed00.bmp
7103,263222,1271227,texture/op00.bmp
2556,262324,1278330,texture/op01.bmp
112675,263224,1280886,texture/op02.bmp
520,66614,1393561,texture/op03.bmp
9619,66614,1394081,texture/op_npc.bmp
10803,66614,1403700,texture/party.bmp
26791,33792,1414503,texture/thumbs.db
3572,7680,1441294,texture/thumbs.db
191655,2108760,1444866,texture/titletex
9028,263222,1444866,texture/calvin_title.bmp
5267,8192,1453894,texture/thumbs.db
20525,263148,1459161,texture/title.bmp
27147,524488,1479686,texture/title.gim
4191,263222,1506833,texture/title_sky.bmp
125497,786488,1511024,texture/rs[ ` calvin_title.bmp
936,263222,1636521,texture/white.bmp
241924,3126550,1637457,texture/yushatex
241924,3126550,1637457,texture/monsters
9900,66614,1637457,texture/boss01.bmp
26540,263222,1647357,texture/boss02.bmp
11436,262420,1673897,texture/boss03.bmp
14306,263222,1685333,texture/boss04.bmp
11124,263222,1699639,texture/boss05.bmp
10084,263222,1710763,texture/boss06.bmp
17491,263222,1720847,texture/boss07.bmp
11365,263222,1738338,texture/boss08.bmp
11062,263222,1749703,texture/boss09.bmp
26389,263222,1760765,texture/boss10.bmp
3827,66614,1787154,texture/enemy01.bmp
6069,66614,1790981,texture/enemy02.bmp
7985,66614,1797050,texture/enemy03.bmp
8307,66614,1805035,texture/enemy04.bmp
6152,66614,1813342,texture/enemy05.bmp
9480,66614,1819494,texture/enemy06.bmp
5785,66614,1828974,texture/enemy07.bmp
8341,66614,1834759,texture/enemy08.bmp
9403,66614,1843100,texture/enemy09.bmp
7649,66614,1852503,texture/enemy10.bmp
19229,25600,1860152,texture/thumbs.db

 

Posted
On 2025/4/20 at AM8点48分, imranUSK said:

我尝试从文件中提取文件,但由于经验不足,无法成功。希望有人能帮我提取文件。我很乐意为您提供帮助。

研究成果.rar 1.71 MB · 9 downloads

This script should be better handled

import struct
import zlib
import sys
from pathlib import Path

def parse_index(input_path):
    index_data = []
    current_path = []
    
    with open(input_path, 'rb') as f:
        f.seek(44)
        
        while True:
            pos = f.tell()
            
            entry_type = struct.unpack('<H', f.read(2))[0]
            name_length = struct.unpack('<H', f.read(2))[0]
            
            if entry_type == 1:
                f.read(24)
                folder_name = f.read(name_length).decode('latin-1').rstrip('\x00')
                current_path = [folder_name]
            
            elif entry_type == 0:
                f.read(8)
                file_size = struct.unpack('<I', f.read(4))[0]
                uncompressed_size = struct.unpack('<I', f.read(4))[0]
                f.read(4)
                offset = struct.unpack('<I', f.read(4))[0]
                filename = f.read(name_length).decode('latin-1').rstrip('\x00')
                
                full_path = '/'.join(current_path + [filename])
                index_data.append({
                    'path': full_path,
                    'offset': offset,
                    'size': file_size,
                    'uncompressed_size': uncompressed_size
                })
            
            else:
                break
            
            if f.tell() >= os.path.getsize(input_path):
                break
    
    return index_data

def extract_files(input_path, output_dir, index_data):
    header_patterns = {
        b'\x04\x00\x00\x00': 8,
        b'\x08\x00\x00\x00': 12,
        b'\x0C\x00\x00\x00': 16,
        b'\x10\x00\x00\x00': 20,
        b'\x14\x00\x00\x00': 24,
        b'\x24\x00\x00\x00': 40,
        b'\x34\x00\x00\x00': 56
    }

    with open(input_path, 'rb') as f:
        for entry in index_data:
            print(f"{entry['offset']} {entry['size']} {entry['path']}")
            
            output_path = Path(output_dir) / entry['path']
            output_path.parent.mkdir(parents=True, exist_ok=True)
            
            f.seek(entry['offset'])
            full_data = f.read(entry['size'])
            
            skip_bytes = 0
            for pattern, length in header_patterns.items():
                if full_data.startswith(pattern):
                    skip_bytes = length
                    break
            
            compressed_data = full_data[skip_bytes:]
            
            try:
                decompressed = zlib.decompress(compressed_data)
                with open(output_path, 'wb') as out:
                    out.write(decompressed)
            except Exception:
                pass

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: python jrz.py <input_file> <output_dir>")
        sys.exit(1)
        
    input_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    import os
    index_data = parse_index(input_file)
    extract_files(input_file, output_dir, index_data)

 

jrz.py

  • Like 2
Posted (edited)
1 hour ago, imranUSK said:

Can the file be returned to its original state after modifying it?

Some files have returned to their original state, but for those unknown files, you may need to consult more game information to determine what they represent, whether they are model, texture or audio or encrypted, to determine if there is a research direction.
If there is no problem, you can tag the solution

Edited by wq223
Posted
31 minutes ago, wq223 said:

Some files have returned to their original state, but for those unknown files, you may need to consult more game information to determine what they represent, whether they are model, texture or audio or encrypted, to determine if there is a research direction.
If there is no problem, you can tag the solution

I asked you because I'm working on a project to translate the game into Arabic.

So I'll only replace the textures and text.

I don't need the models or sounds.

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...