Initial extraction support

This commit is contained in:
Mc-muffin
2023-09-06 04:02:55 -05:00
parent 929910e737
commit 1caac0f613
2 changed files with 143 additions and 10 deletions

View File

@@ -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:

View File

@@ -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)