jh_extract.py

#508
Raw
Created
June 19, 2022, 4:12 a.m.
Expires
Never
Size
4.9 KB
Hits
233
Syntax
Python
Private
✗ No
print(""" 
    Extractor for Jupiter Hell archives
    
    Place this script where you want the data folder to be extracted (creates /data/ folder).
    
""")
import zlib,os,struct

def FNV1a_64(text):
    v, prime, mask = 0xCBF29CE484222325, 0x100000001B3, 0xFFFFFFFFFFFFFFFF
    for c in text:
        v = (v ^ ord(c)) * prime
    v = v & mask
    return v
    
def extract_NVC(arc_path,pathlist,output_directory,extract_unknown):
    entries = {}
    hash_to_path = {}
    if output_directory[-1] not in ("/","\\"):
        output_directory += "/"
    ## Hash paths in pathlist
    for p in pathlist:
        hash = FNV1a_64(p)
        hash_to_path[hash] = p
    with open(arc_path,"rb") as f:
        ## helper
        def read_entry():
            hash = struct.unpack('<Q',f.read(8))[0]
            offset = struct.unpack('<I',f.read(4))[0]
            length_unc = struct.unpack('<I',f.read(4))[0]
            length_compressed = struct.unpack('<I',f.read(4))[0]
            compression = struct.unpack('<I',f.read(4))[0]
            entries[hash] = (offset,length_compressed, compression)
            
        ## Archive signature
        if f.read(8) != b"nvc1d\x00\x00\x00": raise RuntimeError(".nvc signature not found.")
        num_entries = struct.unpack('<I',f.read(4))[0]
        ## List all the entries - later accessed by hashes
        for x in range(num_entries):
            read_entry()
            
        ## helper
        def read_file(hash):
            offset,length,compression = entries[hash]
            try:
                f.seek(offset)
                if compression == 1: ## Compressed
                    return zlib.decompress(f.read(length)), None
                elif compression == 3: ## Encrypted
                    return f.read(length), "Encrypted file: "
                elif compression == 0:
                    return f.read(length), None
                else:
                    return f.read(length), f"Unknown compression type hash: {hash} compression type: {compression}: "
            except:
                f.seek(offset)
                return f.read(length), "Failure to decompress: "
            return data
        extracted = 0
        for hash in entries:
            if extract_unknown or hash in hash_to_path:
                data,failure = read_file(hash)
                if hash in hash_to_path:
                    path = hash_to_path[hash]
                else:
                    if data[:4] == b"\x89PNG":
                        folder = "data/unknown_png/"
                        ext = ".png"
                    elif data[:4] == b"nmf1":
                        folder = "data/unknown_nmd/"
                        ext = ".nmd"
                    elif data[:4] == b"OggS":
                        folder = "data/unknown_ogg/"
                        ext = ".ogg"
                    elif data[:4] == b"RIFF":
                        folder = "data/unknown_wav/"
                        ext = ".wav"
                    else:
                        folder = "data/unknown/"
                        ext = ".unknown"
                    path = f"{folder}{str(hash)}{ext}"
                if failure:
                    print(f"  {failure} {path}")
                    path += ".fail"
                
                ##Output
                out_path = output_directory+path
                try:
                    os.makedirs(os.path.dirname(out_path), exist_ok=True)
                except OSError as exc:
                    if exc.errno != errno.EEXIST:
                        raise
                with open(out_path,"wb") as f_out:
                    f_out.write(data)
                    extracted+=1
        print(f"Extracted {extracted} files from {arc_path}")

if __name__ == "__main__":
    script_path = f"{os.path.dirname(os.path.realpath(__file__))}/"
    archives_path = script_path
    listfile_path = f"{script_path}/listfile.txt"
    while not os.path.isfile(listfile_path):
        directory = os.path.dirname(f"{input('Listfile directory: ')}/")
        listfile_path = f"{directory}/listfile.txt"
        
    core_path = f"{archives_path}/core.nvc"
    while not os.path.isfile(core_path):
        archives_path = os.path.dirname(f"{input('Jupiter Hell directory: ')}/")
        core_path = f"{archives_path}/core.nvc"
    assets_path = f"{archives_path}/assets.nvc"
    
    output_path = script_path
    
    ##Read listfile for known paths
    known_paths = []
    with open(listfile_path,"r") as f:
        for line in f.readlines():
            line = line.strip()
            known_paths.append(line)
            
    ##Open archives
    consent_unknown = bool(input("Extract files with unknown paths? Y/N: ") == "Y")
    print("")
    extract_NVC(arc_path=core_path, pathlist=known_paths, output_directory=output_path, extract_unknown=consent_unknown)
    print("")
    extract_NVC(arc_path=assets_path, pathlist=known_paths, output_directory=output_path, extract_unknown=consent_unknown)