Skip to content

Instantly share code, notes, and snippets.

@daemongh
Last active January 27, 2024 15:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save daemongh/1eb663f7592487d44071dfdf9df1f4d9 to your computer and use it in GitHub Desktop.
Save daemongh/1eb663f7592487d44071dfdf9df1f4d9 to your computer and use it in GitHub Desktop.
Unpack .fuk files (Railway Empire, Patrician, Port Royale)
import os
import shutil
import sys
import zlib
def read_uint(file):
read_bytes = file.read(4)
result = 0
result += read_bytes[0]
result += read_bytes[1] * 256
result += read_bytes[2] * 256 * 256
result += read_bytes[3] * 256 * 256 * 256
# FFFF FFFF check, we'll set it to negative -1
# this is only appears in the descriptor's parent element
if result == 4294967295:
result = -1
return result
def read_uints(file, size):
result = []
for x in range(size):
result.append(read_uint(file))
return result
def read_string(file):
result_bytes = b""
byte = file.read(1)
while byte != b"\x00":
result_bytes += byte
byte = file.read(1)
return str(result_bytes, "utf-8", "ignore")
def read_file_list(file, length):
index = 0
file_list = {}
while index < length:
string = read_string(file)
file_list[index] = string
index += len(string) + 1
return file_list
def read_descriptors(descriptor_data, file_metadata, file_offsets, file_list):
length = int(len(descriptor_data) / 4)
descriptors = []
offset_index = 0
for block_size in range(0, length):
descriptor_index = block_size * 4
data_index = block_size * 3
descriptor = {
"next": descriptor_data[descriptor_index],
"child": descriptor_data[descriptor_index + 1],
"parent": descriptor_data[descriptor_index + 2],
"length": file_metadata[data_index],
"compressed_length": file_metadata[data_index + 1],
"name": file_list[descriptor_data[descriptor_index + 3]],
"offset": file_metadata[data_index + 2]
}
if file_offsets and descriptor["offset"] != 0:
descriptor["offset"] = file_offsets[offset_index] * 16
offset_index += 1
descriptors.append(descriptor)
return descriptors
def parent_name(index, descriptors):
return descriptors[index]["name"]
def descriptor_path(index, descriptors):
path = [descriptors[index]["name"]]
parent_index = descriptors[index]["parent"]
while parent_index != -1:
path.append(parent_name(parent_index, descriptors))
parent_index = descriptors[parent_index]["parent"]
return "/".join(path[::-1])
def extract_files(filename, file, descriptors):
directory = filename.replace(".fuk", "")
if os.path.exists(directory):
print("Directory {} exists, deleting it first...".format(directory))
shutil.rmtree(directory)
os.makedirs(directory)
count = len(descriptors)
for index in range(count):
descriptor = descriptors[index]
output_filename = "{d}/{p}".format(d=directory, p=descriptor_path(index, descriptors)).replace("//", "/")
output_directory = os.path.dirname(output_filename)
if descriptor["length"] == 0 and descriptor["compressed_length"] == 0:
print("({}/{}) Creating directory {}".format(index + 1, count, output_filename))
if not os.path.exists(output_filename):
os.makedirs(output_filename)
else:
length = min(descriptor["length"], descriptor["compressed_length"])
if not os.path.exists(output_directory):
os.makedirs(output_directory)
with open(output_filename, "wb") as output_file:
file.seek(descriptor["offset"])
read_file_data = file.read(length)
print("({}/{}) Extracting {}...".format(index + 1, count, output_filename))
if descriptor["compressed_length"] < descriptor["length"]:
data = zlib.decompress(read_file_data)
else:
data = read_file_data
output_file.write(data)
def process_file(filename):
if not os.path.exists(filename):
print("File {} does not exists...".format(filename))
exit()
with open(filename, "rb") as file:
# first four bytes should be 78 56 34 12 (305419896 as an unsigned decimal)
signature = read_uint(file)
if signature != 305419896:
print("Invalid file signature: {0}".format(signature))
exit()
version = read_uint(file)
print("Version: {0}".format(version))
if version == 32 or version == 0:
flag = version == 32
block_size = read_uint(file)
count = 0 if not flag else read_uint(file)
if block_size <= 0 or flag and count <= 0:
print("File empty")
exit()
descriptor_data_length = read_uint(file)
padding = 0 if not flag else read_uint(file)
# print("Padding: {}".format(padding))
descriptor_data = read_uints(file, block_size * 4)
file_metadata = read_uints(file, block_size * 3)
file_offsets = [] if not flag else read_uints(file, count)
file_list = read_file_list(file, descriptor_data_length)
descriptors = read_descriptors(descriptor_data, file_metadata, file_offsets, file_list)
extract_files(filename, file, descriptors)
else:
print("Unknown file version: {0}".format(version))
exit()
if len(sys.argv) < 2:
print("Need a .fuk file passed in as an argument.")
exit()
fuk_filename = sys.argv[1]
if __name__ == "__main__":
process_file(fuk_filename)
@BorningLife
Copy link

thank you my friend!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment