neoLegacy/tools/pck_pack.py
Fireblade 314a453996 chore: correct dlc loading 'regex' + add extra helper tools to the tools folder
msscmp_extract is made by me idk where i got the other two from
2026-05-20 15:52:52 -04:00

195 lines
3.7 KiB
Python

#!/usr/bin/env python3
import struct
import argparse
import os
import sys
# default endian
ENDIAN = ">"
# defaults
DEFAULT_PCK_TYPE = 3
DEFAULT_XML_VERSION = 4
DEFAULT_ASSET_TYPE = 0
def write_u32(f, value):
f.write(struct.pack(f"{ENDIAN}I", value))
def write_utf16_string(f, text):
"""
PCK strings:
uint32 length
UTF-16 bytes
uint32 padding
"""
encoding = "utf-16-be" if ENDIAN == ">" else "utf-16-le"
encoded = text.encode(encoding)
write_u32(f, len(text))
f.write(encoded)
# padding
write_u32(f, 0)
def collect_folder_assets(folder_path):
assets = []
for root, _, files in os.walk(folder_path):
for file in files:
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, folder_path)
# PCKs usually use backslashes
rel_path = rel_path.replace("/", "\\")
rel_path = rel_path.replace("\\\\", "\\")
with open(full_path, "rb") as f:
data = f.read()
assets.append({
"name": rel_path,
"size": len(data),
"type": DEFAULT_ASSET_TYPE,
"params": {},
"data": data,
})
return assets
def pack_folder_to_pck(input_folder, output_pck):
assets = collect_folder_assets(input_folder)
if not assets:
raise ValueError("No files found in folder")
# parameter lookup table
lookup = [
"PATH",
"TYPE",
"XMLVERSION",
]
print(f"Assets: {len(assets)}")
with open(output_pck, "wb") as f:
# ----- HEADER -----
write_u32(f, DEFAULT_PCK_TYPE)
write_u32(f, len(lookup))
# ----- PARAMETER LOOKUP TABLE -----
for idx, key in enumerate(lookup):
write_u32(f, idx)
write_utf16_string(f, key)
# optional XMLVERSION
if "XMLVERSION" in lookup:
write_u32(f, DEFAULT_XML_VERSION)
# ----- ASSET TABLE -----
write_u32(f, len(assets))
for asset in assets:
write_u32(f, asset["size"])
write_u32(f, asset["type"])
write_utf16_string(f, asset["name"])
print(f"Indexing: {asset['name']} ({asset['size']} bytes)")
# ----- ASSET DATA -----
for asset in assets:
params = asset["params"]
write_u32(f, len(params))
for key, value in params.items():
key_index = lookup.index(key)
write_u32(f, key_index)
write_utf16_string(f, value)
f.write(asset["data"])
print(f"Writing: {asset['name']}")
print(f"\nDone! Wrote: {output_pck}")
def main():
global ENDIAN
parser = argparse.ArgumentParser(
description="Pack a folder into a Minecraft Legacy Console .pck"
)
parser.add_argument(
"input",
help="Input folder"
)
parser.add_argument(
"-o",
"--output",
help="Output .pck filename"
)
parser.add_argument(
"--little",
action="store_true",
help="Write little-endian PCK"
)
args = parser.parse_args()
input_path = args.input
if not os.path.isdir(input_path):
print(f"Input folder not found: {input_path}")
sys.exit(1)
if args.little:
ENDIAN = "<"
output_path = args.output
if not output_path:
output_path = os.path.basename(
os.path.normpath(input_path)
) + ".pck"
try:
pack_folder_to_pck(input_path, output_path)
except Exception as e:
print(f"\nERROR: {e}")
sys.exit(1)
if __name__ == "__main__":
main()