Correct root level for Data/Data extractions
This commit is contained in:
parent
519093f797
commit
f1a7bb93e6
3 changed files with 192 additions and 63 deletions
|
|
@ -94,6 +94,8 @@ from util.project_scanner import (
|
|||
wolf_has_maps,
|
||||
wolf_maps_dir,
|
||||
wolf_maps_packed,
|
||||
wolf_repair_nested_data_dir,
|
||||
wolf_unpack_out_dir,
|
||||
)
|
||||
from util.vocab import read_game_vocab, write_game_vocab
|
||||
|
||||
|
|
@ -1406,20 +1408,38 @@ class WolfWorkflowTab(QWidget):
|
|||
if interactive:
|
||||
QMessageBox.information(self, "Unpack", "No .wolf archives found to unpack.")
|
||||
return
|
||||
out_dir = root / "Data"
|
||||
root = Path(self._game_root)
|
||||
|
||||
def task(log, progress=None):
|
||||
from collections import defaultdict
|
||||
|
||||
from util import wolfdawn
|
||||
log(f"Unpacking {len(archives)} archive(s) into {out_dir} …")
|
||||
res = wolfdawn.unpack_all(
|
||||
[str(a) for a in archives],
|
||||
str(out_dir),
|
||||
log_fn=log,
|
||||
progress_fn=progress,
|
||||
progress_total=len(archives),
|
||||
)
|
||||
if not res.ok:
|
||||
return False, f"unpack-all exited {res.returncode}"
|
||||
|
||||
groups: dict[Path, list[Path]] = defaultdict(list)
|
||||
for arc in archives:
|
||||
groups[wolf_unpack_out_dir(root, arc)].append(arc)
|
||||
|
||||
total = len(archives)
|
||||
done = 0
|
||||
for target, group in groups.items():
|
||||
log(f"Unpacking {len(group)} archive(s) into {target} …")
|
||||
res = wolfdawn.unpack_all(
|
||||
[str(a) for a in group],
|
||||
str(target),
|
||||
log_fn=log,
|
||||
progress_fn=(
|
||||
(lambda c, _t, label, base=done: progress(base + min(c, 1), total, label))
|
||||
if progress
|
||||
else None
|
||||
),
|
||||
progress_total=len(group),
|
||||
)
|
||||
done += len(group)
|
||||
if progress:
|
||||
progress(done, total, f"Unpacked {len(group)} archive(s)")
|
||||
if not res.ok:
|
||||
return False, f"unpack-all exited {res.returncode}"
|
||||
wolf_repair_nested_data_dir(root)
|
||||
return True, "Unpacked archives into Data/."
|
||||
|
||||
self._run_task(task, on_done=on_done)
|
||||
|
|
|
|||
|
|
@ -2,59 +2,118 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from util.project_scanner import (
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
os.chdir(ROOT)
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from util.project_scanner import ( # noqa: E402
|
||||
detect_wolf_layout,
|
||||
find_wolf_text_archives,
|
||||
wolf_has_maps,
|
||||
wolf_maps_dir,
|
||||
wolf_maps_packed,
|
||||
wolf_repair_nested_data_dir,
|
||||
wolf_unpack_out_dir,
|
||||
)
|
||||
|
||||
|
||||
def test_wolf_maps_dir_prefers_mapdata_subfolder(tmp_path: Path):
|
||||
data = tmp_path / "Data"
|
||||
(data / "MapData").mkdir(parents=True)
|
||||
assert wolf_maps_dir(data) == data / "MapData"
|
||||
class WolfProjectScannerTests(unittest.TestCase):
|
||||
def test_wolf_maps_dir_prefers_mapdata_subfolder(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
data = Path(raw) / "Data"
|
||||
(data / "MapData").mkdir(parents=True)
|
||||
self.assertEqual(wolf_maps_dir(data), data / "MapData")
|
||||
|
||||
def test_wolf_maps_dir_falls_back_to_data_root(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
data = Path(raw) / "Data"
|
||||
data.mkdir()
|
||||
self.assertEqual(wolf_maps_dir(data), data)
|
||||
|
||||
def test_wolf_has_maps(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
data = Path(raw) / "Data"
|
||||
maps = data / "MapData"
|
||||
maps.mkdir(parents=True)
|
||||
self.assertFalse(wolf_has_maps(data))
|
||||
(maps / "town.mps").write_bytes(b"")
|
||||
self.assertTrue(wolf_has_maps(data))
|
||||
|
||||
def test_find_wolf_text_archives_in_data_dir(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
data = base / "Data"
|
||||
data.mkdir()
|
||||
(data / "MapData.wolf").write_bytes(b"")
|
||||
(data / "BasicData.wolf").write_bytes(b"")
|
||||
found = find_wolf_text_archives(base, data)
|
||||
self.assertEqual(found["MapData"], data / "MapData.wolf")
|
||||
self.assertEqual(found["BasicData"], data / "BasicData.wolf")
|
||||
|
||||
def test_wolf_maps_packed_when_archive_present(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
data = base / "Data"
|
||||
(data / "BasicData" / "CommonEvent.dat").mkdir(parents=True)
|
||||
(data / "MapData.wolf").write_bytes(b"")
|
||||
self.assertTrue(wolf_maps_packed(base, data))
|
||||
|
||||
def test_wolf_maps_packed_false_when_loose_maps_exist(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
data = base / "Data"
|
||||
maps = data / "MapData"
|
||||
maps.mkdir(parents=True)
|
||||
(maps / "field.mps").write_bytes(b"")
|
||||
(data / "MapData.wolf").write_bytes(b"")
|
||||
self.assertFalse(wolf_maps_packed(base, data))
|
||||
|
||||
def test_wolf_unpack_out_dir_root_data_wolf(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
(base / "Data.wolf").write_bytes(b"")
|
||||
self.assertEqual(wolf_unpack_out_dir(base, base / "Data.wolf"), base)
|
||||
|
||||
def test_wolf_unpack_out_dir_nested_text_archives(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
data = base / "Data"
|
||||
data.mkdir()
|
||||
arc = data / "MapData.wolf"
|
||||
arc.write_bytes(b"")
|
||||
self.assertEqual(wolf_unpack_out_dir(base, arc), data)
|
||||
|
||||
def test_wolf_repair_nested_data_dir(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
outer = base / "Data"
|
||||
inner = outer / "Data"
|
||||
basic = inner / "BasicData"
|
||||
basic.mkdir(parents=True)
|
||||
(basic / "CommonEvent.dat").write_bytes(b"")
|
||||
self.assertTrue(wolf_repair_nested_data_dir(base))
|
||||
self.assertTrue((outer / "BasicData" / "CommonEvent.dat").is_file())
|
||||
self.assertFalse(inner.exists())
|
||||
|
||||
def test_detect_wolf_layout_after_nested_repair(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
base = Path(raw)
|
||||
outer = base / "Data"
|
||||
inner = outer / "Data"
|
||||
basic = inner / "BasicData"
|
||||
basic.mkdir(parents=True)
|
||||
(basic / "CommonEvent.dat").write_bytes(b"")
|
||||
(base / "Data.wolf").write_bytes(b"")
|
||||
info = detect_wolf_layout(base)
|
||||
self.assertTrue(info["unpacked"])
|
||||
self.assertEqual(info["data_dir"], outer)
|
||||
|
||||
|
||||
def test_wolf_maps_dir_falls_back_to_data_root(tmp_path: Path):
|
||||
data = tmp_path / "Data"
|
||||
data.mkdir()
|
||||
assert wolf_maps_dir(data) == data
|
||||
|
||||
|
||||
def test_wolf_has_maps(tmp_path: Path):
|
||||
data = tmp_path / "Data"
|
||||
maps = data / "MapData"
|
||||
maps.mkdir(parents=True)
|
||||
assert not wolf_has_maps(data)
|
||||
(maps / "town.mps").write_bytes(b"")
|
||||
assert wolf_has_maps(data)
|
||||
|
||||
|
||||
def test_find_wolf_text_archives_in_data_dir(tmp_path: Path):
|
||||
data = tmp_path / "Data"
|
||||
data.mkdir()
|
||||
(data / "MapData.wolf").write_bytes(b"")
|
||||
(data / "BasicData.wolf").write_bytes(b"")
|
||||
found = find_wolf_text_archives(tmp_path, data)
|
||||
assert found["MapData"] == data / "MapData.wolf"
|
||||
assert found["BasicData"] == data / "BasicData.wolf"
|
||||
|
||||
|
||||
def test_wolf_maps_packed_when_archive_present(tmp_path: Path):
|
||||
data = tmp_path / "Data"
|
||||
data.mkdir()
|
||||
(data / "BasicData" / "CommonEvent.dat").mkdir(parents=True)
|
||||
(data / "MapData.wolf").write_bytes(b"")
|
||||
assert wolf_maps_packed(tmp_path, data)
|
||||
|
||||
|
||||
def test_wolf_maps_packed_false_when_loose_maps_exist(tmp_path: Path):
|
||||
data = tmp_path / "Data"
|
||||
maps = data / "MapData"
|
||||
maps.mkdir(parents=True)
|
||||
(maps / "field.mps").write_bytes(b"")
|
||||
(data / "MapData.wolf").write_bytes(b"")
|
||||
assert not wolf_maps_packed(tmp_path, data)
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -172,16 +172,66 @@ def find_wolf_archives(game_root: str | Path) -> list[Path]:
|
|||
return unique
|
||||
|
||||
|
||||
def _wolf_dir_has_loose_data(data_dir: Path) -> bool:
|
||||
"""True when *data_dir* looks like an unpacked WOLF Data folder."""
|
||||
if not data_dir.is_dir():
|
||||
return False
|
||||
for marker in _WOLF_LOOSE_MARKERS:
|
||||
if (data_dir / marker).is_file():
|
||||
return True
|
||||
if list(data_dir.glob("*.mps")) or list((data_dir / "MapData").glob("*.mps")):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def wolf_unpack_out_dir(game_root: str | Path, archive: str | Path) -> Path:
|
||||
"""Return the ``-o`` directory for ``wolf unpack-all`` on *archive*.
|
||||
|
||||
``wolf`` unpacks ``Name.wolf`` to ``<out>/Name/``. A root-level ``Data.wolf``
|
||||
must therefore unpack to the game root (yielding ``Data/``), not into an
|
||||
existing ``Data/`` folder (which would incorrectly create ``Data/Data/``).
|
||||
"""
|
||||
game_root = Path(game_root).resolve()
|
||||
archive = Path(archive).resolve()
|
||||
if archive.stem == "Data" and archive.parent == game_root:
|
||||
return game_root
|
||||
data_dir = game_root / "Data"
|
||||
if data_dir.is_dir() and archive.parent == data_dir:
|
||||
return data_dir
|
||||
return archive.parent
|
||||
|
||||
|
||||
def wolf_repair_nested_data_dir(game_root: str | Path) -> bool:
|
||||
"""Hoist ``Data/Data/`` to ``Data/`` after a mistaken ``Data.wolf`` unpack.
|
||||
|
||||
Returns True when the nested layout was repaired.
|
||||
"""
|
||||
outer = Path(game_root) / "Data"
|
||||
inner = outer / "Data"
|
||||
if not inner.is_dir() or not _wolf_dir_has_loose_data(inner):
|
||||
return False
|
||||
if any(p.name != "Data" for p in outer.iterdir()):
|
||||
return False
|
||||
for child in list(inner.iterdir()):
|
||||
dest = outer / child.name
|
||||
if dest.exists():
|
||||
if dest.is_dir():
|
||||
shutil.rmtree(dest)
|
||||
else:
|
||||
dest.unlink()
|
||||
shutil.move(str(child), str(dest))
|
||||
try:
|
||||
inner.rmdir()
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _wolf_loose_data_dir(root: Path) -> Optional[Path]:
|
||||
"""Return the loose (unpacked) WOLF Data/ folder if it looks unpacked."""
|
||||
wolf_repair_nested_data_dir(root)
|
||||
for data_dir in (root / "Data", root):
|
||||
if not data_dir.is_dir():
|
||||
continue
|
||||
for marker in _WOLF_LOOSE_MARKERS:
|
||||
if (data_dir / marker).is_file():
|
||||
return data_dir
|
||||
# A folder with .mps maps (directly or under MapData/) is unpacked WOLF.
|
||||
if list(data_dir.glob("*.mps")) or list((data_dir / "MapData").glob("*.mps")):
|
||||
if _wolf_dir_has_loose_data(data_dir):
|
||||
return data_dir
|
||||
return None
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue