You've already forked PythonLib
mirror of
https://github.com/lifebottle/PythonLib.git
synced 2026-02-13 15:25:50 -08:00
Initial extraction support
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from pythonlib.games import ToolsNDX, ToolsTOR
|
||||
from pythonlib.games import ToolsNDX, ToolsTODDC, ToolsTOR
|
||||
|
||||
SCRIPT_VERSION = "0.0.3"
|
||||
|
||||
@@ -13,10 +13,10 @@ def get_arguments(argv=None):
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--game",
|
||||
choices=["TOR", "NDX"],
|
||||
choices=["TOR", "TODDC", "NDX"],
|
||||
required=True,
|
||||
metavar="game",
|
||||
help="Options: TOR, NDX",
|
||||
help="Options: TOR, TODDC, NDX",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
@@ -141,6 +141,8 @@ def getTalesInstance(args, game_name):
|
||||
talesInstance = ToolsTOR.ToolsTOR(
|
||||
args.project.resolve(), insert_mask, args.only_changed
|
||||
)
|
||||
elif game_name == "TODDC":
|
||||
talesInstance = ToolsTODDC.ToolsTODDC(args.project.resolve())
|
||||
elif game_name == "NDX":
|
||||
talesInstance = ToolsNDX.ToolsNDX("TBL_All.json")
|
||||
else:
|
||||
|
||||
@@ -1,10 +1,141 @@
|
||||
import shutil
|
||||
import struct
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import pycdlib
|
||||
import pyjson5 as json
|
||||
from tqdm import tqdm
|
||||
|
||||
import pythonlib.utils.comptolib as comptolib
|
||||
|
||||
from .ToolsTales import ToolsTales
|
||||
|
||||
|
||||
class ToolsTODDC(ToolsTales):
|
||||
|
||||
|
||||
def __init__(self, tbl):
|
||||
|
||||
super().__init__("TODDC", tbl, "Tales-of-Destiny-DC")
|
||||
|
||||
|
||||
def __init__(self, project_file: Path) -> None:
|
||||
base_path = project_file.parent
|
||||
self.jsonTblTags = {}
|
||||
self.ijsonTblTags = {}
|
||||
with open(project_file, encoding="utf-8") as f:
|
||||
jsonRaw = json.load(f)
|
||||
|
||||
self.paths: dict[str, Path] = {
|
||||
k: base_path / v for k, v in jsonRaw["paths"].items()
|
||||
}
|
||||
|
||||
self.main_exe_name = jsonRaw["main_exe_name"]
|
||||
self.asm_file = jsonRaw["asm_file"]
|
||||
|
||||
with open(self.paths["encoding_table"], encoding="utf-8") as f:
|
||||
jsonRaw = json.load(f)
|
||||
|
||||
for k, v in jsonRaw.items():
|
||||
self.jsonTblTags[k] = {int(k2, 16): v2 for k2, v2 in v.items()}
|
||||
|
||||
for k, v in self.jsonTblTags.items():
|
||||
self.ijsonTblTags[k] = {v2: k2 for k2, v2 in v.items()}
|
||||
self.id = 1
|
||||
|
||||
|
||||
def get_datbin_file_data(self) -> list[tuple[int, int]]:
|
||||
tbl_path = self.paths["original_files"] / "DAT.TBL"
|
||||
with open(tbl_path, "rb") as tbl:
|
||||
blob = tbl.read()
|
||||
|
||||
pointers = struct.unpack(f"<{len(blob)//4}I", blob)
|
||||
file_data: list[tuple[int, int]] = []
|
||||
for c, n in zip(pointers[::2], pointers[2::2]):
|
||||
remainder = c & 0x3F
|
||||
start = c & 0xFFFFFFC0
|
||||
end = (n & 0xFFFFFFC0) - remainder
|
||||
file_data.append((start, end - start))
|
||||
|
||||
return file_data
|
||||
|
||||
|
||||
# Extract the file DAT.BIN to the different directorties
|
||||
def extract_main_archive(self) -> None:
|
||||
dat_bin_path = self.paths["extracted_files"] / "DAT"
|
||||
dat_bin_path.mkdir(exist_ok=True)
|
||||
|
||||
self.clean_folder(dat_bin_path)
|
||||
|
||||
print("Extracting DAT.BIN files...")
|
||||
with open(self.paths["original_files"] / "DAT.BIN", "rb") as f:
|
||||
for i, (offset, size) in enumerate(tqdm(self.get_datbin_file_data(), desc="Extracting files", unit="file")):
|
||||
|
||||
# Ignore 0 byte files
|
||||
if size == 0:
|
||||
continue
|
||||
|
||||
f.seek(offset, 0)
|
||||
data = f.read(size)
|
||||
|
||||
if comptolib.is_compressed(data):
|
||||
c_type = struct.unpack("<b", data[:1])[0]
|
||||
data = comptolib.decompress_data(data)
|
||||
extension = self.get_extension(data)
|
||||
fname = f"{i:05d}.{c_type}.{extension}"
|
||||
else:
|
||||
extension = self.get_extension(data)
|
||||
fname = f"{i:05d}.{extension}"
|
||||
|
||||
final_path = dat_bin_path / extension.upper()
|
||||
final_path.mkdir(exist_ok=True)
|
||||
|
||||
with open(final_path / fname, "wb") as output:
|
||||
output.write(data)
|
||||
|
||||
|
||||
def extract_Iso(self, game_iso: Path) -> None:
|
||||
|
||||
print("Extracting ISO files...")
|
||||
|
||||
iso = pycdlib.PyCdlib()
|
||||
iso.open(str(game_iso))
|
||||
|
||||
extract_to = self.paths["original_files"]
|
||||
self.clean_folder(extract_to)
|
||||
|
||||
files = []
|
||||
for dirname, _, filelist in iso.walk(iso_path="/"):
|
||||
files += [dirname + x for x in filelist]
|
||||
|
||||
for file in files:
|
||||
out_path = extract_to / file[1:]
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with iso.open_file_from_iso(iso_path=file) as f, open(
|
||||
str(out_path).split(";")[0], "wb+"
|
||||
) as output:
|
||||
with tqdm(
|
||||
total=f.length(),
|
||||
desc=f"Extracting {file[1:].split(';')[0]}",
|
||||
unit="B",
|
||||
unit_divisor=1024,
|
||||
unit_scale=True,
|
||||
) as pbar:
|
||||
while data := f.read(0x8000):
|
||||
output.write(data)
|
||||
pbar.update(len(data))
|
||||
|
||||
iso.close()
|
||||
|
||||
# Extract IMS part
|
||||
with open(game_iso, "rb") as f, open(extract_to / "_header.ims", "wb+") as g:
|
||||
f.seek(0)
|
||||
g.write(f.read(274 * 0x800))
|
||||
f.seek(-0x800, 2)
|
||||
g.write(f.read(0x800))
|
||||
|
||||
|
||||
def clean_folder(self, path: Path) -> None:
|
||||
target_files = list(path.iterdir())
|
||||
if len(target_files) != 0:
|
||||
print("Cleaning folder...")
|
||||
for file in target_files:
|
||||
if file.is_dir():
|
||||
shutil.rmtree(file)
|
||||
elif file.name.lower() != ".gitignore":
|
||||
file.unlink(missing_ok=False)
|
||||
|
||||
Reference in New Issue
Block a user