From f5aa1aa96db5178c788341c5e45bc7b5b477354b Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Tue, 29 Jul 2025 20:05:57 -0400 Subject: Fix fov, list all cameras, add ModSlots Signed-off-by: Andrew Opalach --- ModSlots/ModSlots.py | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100755 ModSlots/ModSlots.py (limited to 'ModSlots/ModSlots.py') diff --git a/ModSlots/ModSlots.py b/ModSlots/ModSlots.py new file mode 100755 index 0000000..164fb0e --- /dev/null +++ b/ModSlots/ModSlots.py @@ -0,0 +1,261 @@ +#! /usr/bin/env python3 + +import sys +sys.dont_write_bytecode = True +import os.path +import hashlib +from enum import Enum +from zipfile import ZipFile +#from unrardll import extract, names +#from py7zr import SevenZipFile +from ArmorIDS import ArmorIDS + +USAGE = f'Usage: {sys.argv[0]} ' + +if len(sys.argv) < 3: + print(USAGE) + sys.exit(1) + +Slots = sys.argv[1] +Mods_Directory = sys.argv[2] +Game_Directory = sys.argv[3] +Extract_Directory = f'{Mods_Directory}/extract' +Hash_Dir = f'{Game_Directory}/mod_slots_hash.txt' +List_Dir = f'{Game_Directory}/mod_slots.txt' + +if not os.path.isfile(Slots): + print(f'{Slots} not a file.') + sys.exit(1) + +if not os.path.isdir(Mods_Directory): + print(f'Directory {Mods_Directory} doesn\'t exist.') + sys.exit(1) + +if not os.path.isdir(Extract_Directory): + os.mkdir(Extract_Directory) + +if not os.path.isdir(Game_Directory): + print(f'Directory {Game_Directory} doesn\'t exist.') + sys.exit(1) + +ArmorIDS_Inverted = { v: k for k, v in ArmorIDS.items() } + +class ArchiveType(Enum): + ZIP = 0 + RAR = 1 + SEVEN_ZIP = 2 + +class Mod(): + def __init__(self, path): + m = hashlib.sha256() + m.update(open(path, 'rb').read()) + self.hash = m.hexdigest() + ext = path.split('.')[-1].lower() + if ext == 'zip': + self.type = ArchiveType.ZIP + self.obj = ZipFile(path, 'r') + self.files = [] + for file in self.obj.infolist(): + if file.filename[-1] == '/': + continue + self.files.append(file.filename) + elif ext == 'rar': + self.type = ArchiveType.RAR + pass + elif ext == '7z': + self.type = ArchiveType.SEVEN_ZIP + pass + self.toplevels = [] + self.roots = [] + self.ignored = [] + self.overrides = {} + self.map = {} + + def get_id_from_subpath(self, sub): + for armor_id in ArmorIDS.keys(): + armor_id = armor_id[2:] + search = sub.find(armor_id) + if search >= 0: + return sub[search:search + 8] + + def substitute_override_name(self, name): + if name[0] == '[': + name = name[1:-1] + if name in ArmorIDS_Inverted.keys(): + name = ArmorIDS_Inverted[name][2:] + return name + + def file_sort_key(self, x): + for i in range(0, len(self.roots)): + if x.startswith(self.roots[i]): + return i + return 0 + + def do_map(self, all_files): + for file in sorted(self.files, key=self.file_sort_key): + ignore_this = False + for ig in self.ignored: + if file.startswith(ig): + ignore_this = True + break + if ignore_this: + continue + path = file # Absolute path within archive. + for root in self.roots: + if file.startswith(root): + nativePC = file.find('nativePC') + if nativePC >= 0: + file = file[nativePC:] + break + if file in self.overrides.keys(): + targets = self.overrides[file] + elif not (file.startswith('nativePC') or file in self.toplevels): + continue + else: + targets = [file] + for i in range(0, len(targets)): + target = targets[i] + # Substitute armor names with their IDs. + for key in self.overrides.keys(): + if target.startswith(key): + name = self.substitute_override_name(self.overrides[key][0]) # Sub has to be first element. + target = target.replace(self.get_id_from_subpath(target), name) + if target in all_files: + print(f'DUPLICATE MAPPING: {target}.') + del targets[i] + continue + targets[i] = target + all_files.extend(targets) + print(path) + for target in targets: + print(' -> ' + target) + self.map[path] = targets + + def add_to_hash(self, m): + output = f'{os.path.join(Extract_Directory, self.hash)}' + if not os.path.isdir(output): + os.mkdir(output) + if self.type == ArchiveType.ZIP: + self.obj.extractall(path=output) + for key in self.map: + m.update(open(os.path.join(output, key), 'rb').read()) + + def write(self, old_list): + output = f'{os.path.join(Extract_Directory, self.hash)}' + with open(List_Dir, 'a+') as l: + for key in self.map: + targets = self.map[key] + for target in targets: + if target in old_list: + old_list.remove(target) + l.write(target + '\n') + inc = Game_Directory + for s in target.split('/')[:-1]: + inc = os.path.join(inc, s) + if not os.path.isdir(inc): + os.mkdir(inc) + target = os.path.join(Game_Directory, target) + if os.path.isfile(target): + os.remove(target) + os.symlink(f'{os.path.join(output, key)}', f'{target}') + + def delete(self): + for key in self.map: + targets = self.map[key] + for target in targets: + target = os.path.join(Game_Directory, target) + if os.path.isfile(target): + os.remove(target) + +def is_armor_override(line): + sp = line.split('/') + if len(sp) >= 3 and sp[2] == 'slg': + return True + if sp[-1] in ['helm', 'body', 'arm', 'wst', 'leg']: + return True + return False + +Mods = [] +Current_Mod = None +Current_Override = None +with open(Slots, 'r') as f: + for line in f.read().splitlines(): + if len(line) == 0 or line[0] == '#': + continue + if line[0] == ';': + if Current_Mod: + Mods.append(Current_Mod) + Current_Mod = None + path = os.path.join(Mods_Directory, line[1:]) + if os.path.isfile(path): + Current_Mod = Mod(path) + else: + print(f'FILE NOT FOUND: {path}') + continue + cmd = '' + if line[0] == '*': + cmd = 'root' + elif line[0] == '<': + cmd = 'ignore' + elif line[0] == ':': + cmd = 'override' + elif line[0] == '>': + cmd = 'value' + if cmd: + line = line[1:] + if line.startswith('pl/'): + line = 'nativePC/' + line + if line.startswith('wp/'): + line = 'nativePC/' + line + if cmd == 'root' and Current_Mod: + Current_Mod.roots.append(line) + elif cmd == 'ignore' and Current_Mod: + Current_Mod.ignored.append(line) + elif cmd == 'override' and Current_Mod: + if not (is_armor_override(line) or line in Current_Mod.files): + print(f'FILE NOT FOUND: {line}') + Current_Mod = None + continue + Current_Override = line + Current_Mod.overrides[Current_Override] = [] + elif cmd == 'value' and Current_Mod: + if Current_Override: + Current_Mod.overrides[Current_Override].append(line) + else: + Current_Mod.toplevels.append(line) + if Current_Mod: + Mods.append(Current_Mod) + Current_Mod = None + +Files = [] +M = hashlib.sha256() +for mod in Mods: + mod.do_map(Files) + mod.add_to_hash(M) + +with open(List_Dir, 'a+') as l: + l.seek(0) + old_list = l.read().splitlines() + l.truncate(0) + +Mode = 'Write' +if Mode == 'Write': + for mod in Mods: + mod.write(old_list) + for old in old_list: + old_target = os.path.join(Game_Directory, old) + if os.path.islink(old_target): + print(f'Removing old link: {old_target}') + os.remove(old_target) + with open(Hash_Dir, 'w+') as f: + f.write(M.hexdigest()) +if Mode == 'Verify': + if os.path.isfile(Hash_Dir): + with open(Hash_Dir, 'r') as f: + if f.read() == M.hexdigest(): + print('Verification passed') + else: + print('Verification failed') +elif Mode == 'Delete': + for mod in Mods: + mod.delete() -- cgit v1.2.3-101-g0448