diff options
Diffstat (limited to 'ModSlots')
| -rwxr-xr-x | ModSlots/ModSlots.py | 164 | ||||
| -rw-r--r-- | ModSlots/cmd.txt | 2 | ||||
| -rw-r--r-- | ModSlots/slots.example.txt | 46 |
3 files changed, 146 insertions, 66 deletions
diff --git a/ModSlots/ModSlots.py b/ModSlots/ModSlots.py index 5cea62b..36e0778 100755 --- a/ModSlots/ModSlots.py +++ b/ModSlots/ModSlots.py @@ -10,18 +10,25 @@ from zipfile import ZipFile #from py7zr import SevenZipFile from ArmorIDS import ArmorIDS -USAGE = f'Usage: {sys.argv[0]} <Slots.txt> <Path_to_Mods> <Path_to_Game>' +# @TODO: +# - Detach files (copy instead of symlink, MHWNewCamera.json) +# - Test example to normal and back +# - More automatic override for the simplest case +# - Support invisible weapons (default no-include) -if len(sys.argv) < 3: +USAGE = f'Usage: {sys.argv[0]} <Write/Verify/Delete> <Slots.txt> <Path_to_Mods> <Path_to_Game>' + +if len(sys.argv) < 4: print(USAGE) sys.exit(1) -Slots = sys.argv[1] -Mods_Directory = sys.argv[2] -Game_Directory = sys.argv[3] +Mode = sys.argv[1].lower() +Slots = sys.argv[2] +Mods_Directory = sys.argv[3] +Game_Directory = sys.argv[4] Extract_Directory = f'{Mods_Directory}/extract' -Hash_Dir = f'{Game_Directory}/mod_slots_hash.txt' -List_Dir = f'{Game_Directory}/mod_slots.txt' +Hash_Path = f'{Game_Directory}/mod_slots_hash.txt' +List_Path = f'{Game_Directory}/mod_slots.txt' if not os.path.isfile(Slots): print(f'{Slots} not a file.') @@ -32,12 +39,26 @@ if not os.path.isdir(Mods_Directory): sys.exit(1) if not os.path.isdir(Extract_Directory): - os.mkdir(Extract_Directory) + try: + os.mkdir(Extract_Directory) + except: + print(f'Failed to create directory for extracted mods at {Extract_Directory}.') + sys.exit(1) if not os.path.isdir(Game_Directory): print(f'Directory {Game_Directory} doesn\'t exist.') sys.exit(1) +Error_Messages = [] +Error_Index = 1 +def Error(msg): + global Error_Messages + global Error_Index + msg = f'{Error_Index}: ' + msg + Error_Messages.append(msg) + Error_Index += 1 + print(msg + '\n^^^^^^^^^^^^^^^^^^^^^^^') + ArmorIDS_Inverted = { v: k for k, v in ArmorIDS.items() } class ArchiveType(Enum): @@ -71,14 +92,6 @@ class Mod(): 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] - return None - def substitute_override_name(self, name): if name[0] == '[': name = name[1:-1] @@ -88,13 +101,28 @@ class Mod(): return None return name + 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] + return None + + def find_overrides_for_file(self, file): + for key in self.overrides.keys(): + search = file.find(key) + if search >= 0: + return self.overrides[key] + return None + 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): + 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: @@ -103,51 +131,48 @@ class Mod(): break if ignore_this: continue - path = file # Absolute path within archive. + path_in_archive = file + # If file is within a defined root, truncate it's path up to nativePC/. for root in self.roots: if file.startswith(root): nativePC = file.find('nativePC') if nativePC >= 0: file = file[nativePC:] break + targets = [] if file in self.overrides.keys(): - targets = self.overrides[file] + targets.extend(self.overrides[file]) elif not (file.startswith('nativePC') or file in self.toplevels): + # If file isn't within nativePC/ and is not explicitly included, ignore it. continue else: - targets = [file] - for i in range(0, len(targets)): - target = targets[i] - invalid = False - # Substitute armor names with their IDs. - for key in self.overrides.keys(): - if target.startswith(key): - override = self.overrides[key][0] # Sub has to be first element. + overrides = self.find_overrides_for_file(file) + if not overrides: + targets.append(file) # Include file as-is. + else: + # Substitute armor names, like '[Anjanath]', with their IDs. + for override in overrides: + target_id = self.get_id_from_subpath(file) + if not target_id: + Error(f'Invalid target: {file}.') + continue mapped_name = self.substitute_override_name(override) if not mapped_name: - print(f'INVALID SUBSTITUTE: {override}.') - invalid = True - break - target_id = self.get_id_from_subpath(target) - if not target_id: - print(f'INVALID TARGET: {target}.') - invalid = True - break - target = target.replace(target_id, mapped_name) - break - if not invalid and target in all_files: - print(f'DUPLICATE MAPPING: {target}.') - invalid = True - if invalid: - del targets[i] - i -= 1 - continue - targets[i] = target - all_files.extend(targets) - print(path) + Error(f'Invalid substitute: {override}.') + continue + targets.append(file.replace(target_id, mapped_name)) + print(path_in_archive) + unique_targets = [] for target in targets: - print(' -> ' + target) - self.map[path] = targets + # @TODO: Allow "important" mapping instead of relying on order? + if target in All_Files: + print(f' (Overwritten)') + else: + unique_targets.append(target) + print(' -> ' + target) + if len(unique_targets) > 0: + All_Files.extend(unique_targets) + self.map[path_in_archive] = unique_targets def add_to_hash(self, m): output = f'{os.path.join(Extract_Directory, self.hash)}' @@ -160,7 +185,7 @@ class Mod(): def write(self, old_list): output = f'{os.path.join(Extract_Directory, self.hash)}' - with open(List_Dir, 'a+') as l: + with open(List_Path, 'a+') as l: for key in self.map: targets = self.map[key] for target in targets: @@ -173,17 +198,22 @@ class Mod(): if not os.path.isdir(inc): os.mkdir(inc) target = os.path.join(Game_Directory, target) - if os.path.isfile(target): + if os.path.islink(target): os.remove(target) - os.symlink(f'{os.path.join(output, key)}', f'{target}') + elif os.path.isfile(target) or os.path.isdir(target): + Error(f'Not deleting non-symlink file: {target}.') + continue + os.symlink(f'{os.path.join(output, key)}', 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): + if os.path.islink(target): os.remove(target) + elif os.path.isfile(target) or os.path.isdir(target): + Error(f'Not deleting non-symlink file: {target}.') def is_armor_override(line): sp = line.split('/') @@ -208,7 +238,7 @@ with open(Slots, 'r') as f: if os.path.isfile(path): Current_Mod = Mod(path) else: - print(f'FILE NOT FOUND: {path}') + Error(f'File not found: {path}') continue cmd = '' if line[0] == '*': @@ -231,7 +261,7 @@ with open(Slots, 'r') as f: 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}') + Error(f'File not found: {line}') Current_Mod = None continue Current_Override = line @@ -245,19 +275,18 @@ with open(Slots, 'r') as f: Mods.append(Current_Mod) Current_Mod = None -Files = [] +All_Files = [] M = hashlib.sha256() for mod in Mods: - mod.do_map(Files) + mod.do_map(All_Files) mod.add_to_hash(M) -with open(List_Dir, 'a+') as l: +with open(List_Path, 'a+') as l: l.seek(0) old_list = l.read().splitlines() l.truncate(0) -Mode = 'Write' -if Mode == 'Write': +if Mode == 'write': for mod in Mods: mod.write(old_list) for old in old_list: @@ -265,15 +294,20 @@ if Mode == 'Write': if os.path.islink(old_target): print(f'Removing old link: {old_target}') os.remove(old_target) - with open(Hash_Dir, 'w+') as f: + with open(Hash_Path, 'w+') as f: f.write(M.hexdigest()) -if Mode == 'Verify': - if os.path.isfile(Hash_Dir): - with open(Hash_Dir, 'r') as f: +if Mode == 'verify': + if os.path.isfile(Hash_Path): + with open(Hash_Path, 'r') as f: if f.read() == M.hexdigest(): print('Verification passed') else: print('Verification failed') -elif Mode == 'Delete': +elif Mode == 'delete': for mod in Mods: mod.delete() + +if len(Error_Messages) > 0: + print('-----------------------') + for err in Error_Messages: + print(err) diff --git a/ModSlots/cmd.txt b/ModSlots/cmd.txt index e276cbd..5d823ff 100644 --- a/ModSlots/cmd.txt +++ b/ModSlots/cmd.txt @@ -1 +1 @@ -./ModSlots.py slots.txt /mnt/hdd/mods/mhw /mnt/ssd/SteamLibrary/steamapps/common/Monster\ Hunter\ World +./ModSlots.py Write slots.txt /mnt/hdd/mods/mhw /mnt/ssd/SteamLibrary/steamapps/common/Monster\ Hunter\ World diff --git a/ModSlots/slots.example.txt b/ModSlots/slots.example.txt new file mode 100644 index 0000000..1e739c9 --- /dev/null +++ b/ModSlots/slots.example.txt @@ -0,0 +1,46 @@ +;Stracker's Loader-1982-4-0-1717679465.zip +>dinput8.dll +>loader.dll +>loader-config.json + +;SharpPluginLoader-0.0.7.2-linux.zip +>msvcrt.dll + +;2024 Patch 15.23 1.25-3473-1-25-1728572867.zip + +;MHWI - Hide main menu ad banner-7826-1-0-1728933976.zip + +;MHWNewCamera_2.0.zip +;Fix_Tent_Animation_Face_Deform.zip + +#;Reshade Hook v1.0-7015-1-0-1704129998.zip +#>amd_ags_x64.dll +#;MHWNo-HUD-No-EffectsFix-v.0.3-8195-0-3-1752188975.zip +#>ShaderToggler.addon64 +##>ShaderToggler.ini + +#;Skin Glossiness Options.zip +#:skin_BM_dry.tex +#>pl/f_equip/mangie/bamboo/skin_BM.tex +# +#;4k Skin Textures.zip +#:4k body texture/skin_NM.tex +#>pl/f_equip/mangie/bamboo/skin_NM.tex +#:4k face textures/f_face_edit_NM.tex +#>pl/f_face/face000/mod/f_face_edit_NM.tex +#:4k face textures/f_face000_RMT_dry.tex +#>pl/f_face/face000/mod/f_face000_RMT.tex + +#;Bamboo Shoot.zip +#:pl/f_equip/pl020_0000/helm +#>[Jyuratodus] +#:pl/f_equip/pl020_0000/body +#>[Jyuratodus] +#:pl/f_equip/pl020_0000/arm +#>[Jyuratodus] +#:pl/f_equip/pl020_0000/wst +#>[Jyuratodus] +#:pl/f_equip/pl020_0000/leg +#>[Jyuratodus] +#:wp/slg/slg020_0000 +#>[Jyuratodus] |