jh_extract.py
#508
- 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)