Fix traffic archive extraction workflow

This commit is contained in:
2026-06-19 20:48:08 -05:00
parent 20daac34e5
commit 355b135d69
3 changed files with 126 additions and 28 deletions
+16 -5
View File
@@ -21,13 +21,24 @@ pack_root="${work_dir}/${mod_name}"
packed_dir="${work_dir}/05_packed"
resource_dir=$(dirname "${resource_path}")
resource_file=$(basename "${resource_path}")
resource_hash=3419764573789342681
mkdir -p "${raw_dir}" "${json_dir}" "${patched_json_dir}" "${patched_raw_dir}" "${pack_root}/${resource_dir}" "${packed_dir}"
mkdir -p "${raw_dir}/${resource_dir}" "${json_dir}" "${patched_json_dir}" "${patched_raw_dir}" "${pack_root}/${resource_dir}" "${packed_dir}"
tools/cp77_toolbox.sh extract "${archive_path}" \
--gamepath "${game_dir}" \
--outpath "${raw_dir}" \
--regex "${resource_regex}"
if [[ -f "${archive_path}" ]]; then
kark_path="${raw_dir}/${resource_file}.kark"
decompressed_base="${raw_dir}/${resource_file}.raw"
python3 tools/extract_archive_segment.py "${archive_path}" "${resource_hash}" "${kark_path}"
tools/cp77_toolbox.sh oodle decompress "${kark_path}" "${decompressed_base}" || true
if [[ ! -f "${decompressed_base}.bin" ]]; then
echo "failed to decompress ${kark_path}" >&2
exit 1
fi
mv "${decompressed_base}.bin" "${raw_dir}/${resource_path}"
else
echo "raw segment extraction needs a single archive file, got: ${archive_path}" >&2
exit 2
fi
tools/cp77_toolbox.sh convert serialize "${raw_dir}" --outpath "${json_dir}"
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Extract a single compressed RED4 archive segment by resource hash."""
from __future__ import annotations
import argparse
import struct
from pathlib import Path
FILE_ENTRY_SIZE = 56
FILE_SEGMENT_SIZE = 16
def parse_hash(value: str) -> int:
return int(value, 0)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("archive", type=Path)
parser.add_argument("resource_hash", type=parse_hash)
parser.add_argument("outpath", type=Path)
args = parser.parse_args()
with args.archive.open("rb") as archive:
header = archive.read(40)
if len(header) != 40:
raise SystemExit("archive header is too short")
magic, _version, index_pos, _index_size, *_rest = struct.unpack("<IIQIQIQ", header)
if magic != 1380009042:
raise SystemExit(f"unexpected archive magic: {magic}")
archive.seek(index_pos + 16)
file_count, segment_count, _dependency_count = struct.unpack("<III", archive.read(12))
found: tuple[int, int] | None = None
for _ in range(file_count):
entry = archive.read(FILE_ENTRY_SIZE)
(
key,
_timestamp,
_flags,
segments_start,
segments_end,
_dependencies_start,
_dependencies_end,
_sha1,
) = struct.unpack("<QqIIIII20s", entry)
if key == args.resource_hash:
if segments_end - segments_start != 1:
raise SystemExit(
f"resource has {segments_end - segments_start} segments; expected 1"
)
found = (segments_start, segments_end)
if found is None:
raise SystemExit(f"resource hash {args.resource_hash} not found")
segments = []
for _ in range(segment_count):
segments.append(struct.unpack("<QII", archive.read(FILE_SEGMENT_SIZE)))
offset, z_size, size = segments[found[0]]
archive.seek(offset)
data = archive.read(z_size)
if len(data) != z_size:
raise SystemExit("archive ended before segment data was fully read")
args.outpath.parent.mkdir(parents=True, exist_ok=True)
args.outpath.write_bytes(data)
print(f"wrote {args.outpath} z_size={z_size} size={size}")
return 0
if __name__ == "__main__":
raise SystemExit(main())