feat(clew): extract Ren'Py source files #19
3
.gitignore
vendored
3
.gitignore
vendored
@@ -20,6 +20,9 @@ Thumbs.db
|
|||||||
# Bazel convenience symlinks
|
# Bazel convenience symlinks
|
||||||
/bazel-*
|
/bazel-*
|
||||||
|
|
||||||
|
# local generated output
|
||||||
|
/output/
|
||||||
|
|
||||||
# language package-manager caches and outputs
|
# language package-manager caches and outputs
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
8
src/clew/BUILD.bazel
Normal file
8
src/clew/BUILD.bazel
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
load("@rules_python//python:defs.bzl", "py_binary")
|
||||||
|
|
||||||
|
py_binary(
|
||||||
|
name = "clew",
|
||||||
|
srcs = ["clew.py"],
|
||||||
|
main = "clew.py",
|
||||||
|
deps = ["//third_party/python:rpycdec"],
|
||||||
|
)
|
||||||
101
src/clew/clew.py
Normal file
101
src/clew/clew.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from collections import deque
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from rpycdec.rpa import extract_rpa
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="clew",
|
||||||
|
description="Extract Ren'Py game files.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--game-root",
|
||||||
|
required=True,
|
||||||
|
type=Path,
|
||||||
|
help="Game installation root to scan.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-root",
|
||||||
|
type=Path,
|
||||||
|
default=Path("output"),
|
||||||
|
help="Output root. Defaults to repository output/.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--overwrite",
|
||||||
|
action="store_true",
|
||||||
|
help="Replace Clew-managed output directories.",
|
||||||
|
)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_output_root(output_root: Path) -> Path:
|
||||||
|
if output_root.is_absolute():
|
||||||
|
return output_root.resolve()
|
||||||
|
|
||||||
|
workspace = os.environ.get("BUILD_WORKSPACE_DIRECTORY")
|
||||||
|
if workspace:
|
||||||
|
return (Path(workspace) / output_root).resolve()
|
||||||
|
|
||||||
|
return output_root.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def get_output_directory(output_root: Path, folder: str) -> Path:
|
||||||
|
directory = output_root / folder
|
||||||
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
return directory
|
||||||
|
|
||||||
|
|
||||||
|
def extract_archive(archive: Path, destination: Path) -> None:
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
with archive.open("rb") as handle:
|
||||||
|
extract_rpa(handle, output_dir=str(destination))
|
||||||
|
|
||||||
|
|
||||||
|
def extract_game_files(game_root: Path, output_path: Path) -> None:
|
||||||
|
queue = deque(game_root.iterdir())
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
item = queue.popleft()
|
||||||
|
destination = output_path / item.relative_to(game_root)
|
||||||
|
|
||||||
|
if item.is_dir():
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
queue.extend(item.iterdir())
|
||||||
|
elif item.is_file() and item.suffix.lower() == ".rpa":
|
||||||
|
extract_archive(item, destination.with_suffix(""))
|
||||||
|
elif item.is_file():
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy2(item, destination)
|
||||||
|
|
||||||
|
|
||||||
|
def run(args: argparse.Namespace) -> None:
|
||||||
|
game_root = args.game_root.resolve()
|
||||||
|
output_root = resolve_output_root(args.output_root)
|
||||||
|
|
||||||
|
if not game_root.is_dir():
|
||||||
|
LOG.critical(f'game root is not a directory: {game_root}')
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
extracted = get_output_directory(output_root, "extracted")
|
||||||
|
extract_game_files(game_root, extracted)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parse_args(sys.argv[1:])
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="[%(asctime)s] %(levelname)s %(filename)s:%(lineno)d | %(message)s",
|
||||||
|
datefmt="%Y-%m-%d][%H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
run(args)
|
||||||
Reference in New Issue
Block a user