summaryrefslogtreecommitdiff
path: root/ModSlots/ModSlots.py
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-07-29 20:05:57 -0400
committerAndrew Opalach <andrew@akon.city> 2025-07-29 20:05:57 -0400
commitf5aa1aa96db5178c788341c5e45bc7b5b477354b (patch)
treeef4af216a775d1553b5025a21c3ac80c8c7722b6 /ModSlots/ModSlots.py
parenta9d16fd96dd6e9db075d6a5458ab7f72ca733715 (diff)
downloadNewCamera-f5aa1aa96db5178c788341c5e45bc7b5b477354b.tar.gz
NewCamera-f5aa1aa96db5178c788341c5e45bc7b5b477354b.tar.bz2
NewCamera-f5aa1aa96db5178c788341c5e45bc7b5b477354b.zip
Fix fov, list all cameras, add ModSlots
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'ModSlots/ModSlots.py')
-rwxr-xr-xModSlots/ModSlots.py261
1 files changed, 261 insertions, 0 deletions
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]} <Slots.txt> <Path_to_Mods> <Path_to_Game>'
+
+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()