#! /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()