Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
if table not in drop_table:
del ref_drop[table]
else:
for item in set(ref_drop[table]["items"].keys()):
if item not in drop_table[table]["items"]:
del ref_drop[table]["items"][item]
util.dict_merge(ref_drop, drop_table)
drop_table = ref_drop
except (FileNotFoundError, AttributeError, RuntimeError):
pass
actor_name = re.search(r"Pack\/(.+)\.sbactorpack", file).groups()[0]
pio = _dict_to_drop(drop_table)
util.inject_files_into_actor(actor_name, {file.split("//")[-1]: pio.to_binary()})
class DropMerger(mergers.Merger):
NAME: str = "drops"
def __init__(self):
super().__init__(
"drop merger", "Merges changes to drop tables", "drops.json", options={}
)
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]):
drops = {f for f in modded_files if isinstance(f, str) and f.endswith(".bdrop")}
if not drops:
return {}
print("Logging changes to drop tables...")
pool = self._pool or Pool()
diffs = {}
for result in pool.map(partial(log_drop_file, mod_dir=mod_dir), drops):
diffs.update(result)
rstb_path = util.get_master_modpack_dir() / 'content' / 'System' / 'Resource' / \
'ResourceSizeTable.product.srsizetable'
if not rstb_path.exists():
rstb_path.parent.mkdir(parents=True, exist_ok=True)
with rstb_path.open('wb') as r_file:
with io.BytesIO() as buf:
table.write(buf, True)
r_file.write(util.compress(buf.getvalue()))
rstb_log = util.get_master_modpack_dir() / 'logs' / 'master-rstb.log'
rstb_log.parent.mkdir(parents=True, exist_ok=True)
with rstb_log.open('w', encoding='utf-8') as r_file:
r_file.write('\n'.join([change[0].strip() for change in rstb_changes]))
class RstbMerger(mergers.Merger):
""" A merger for the ResourceSizeTable.product.srsizetable """
NAME: str = 'rstb'
def __init__(self, guess: bool = True, leave: bool = False, shrink: bool = False):
super().__init__('RSTB merge', 'Merges changes to ResourceSizeTable.product.srsizetable',
'rstb.log')
self._options = {
'no_guess': not guess,
'leave': leave,
'shrink': shrink
}
def generate_diff(self, mod_dir: Path, modded_files: List[Path]):
rstb_diff = {}
open_sarcs = {}
for file in modded_files:
from functools import lru_cache
from pathlib import Path
from typing import List, Union
import oead
from bcml import mergers, util
def get_stock_quests() -> oead.byml.Array:
title_sarc = oead.Sarc(util.get_game_file("Pack/TitleBG.pack").read_bytes())
return oead.byml.from_binary(
util.decompress(title_sarc.get_file("Quest/QuestProduct.sbquestpack").data)
)
class QuestMerger(mergers.Merger):
NAME: str = "quests"
def __init__(self):
super().__init__(
"quests", "Merges changes to Quest.product.byml", "quests.yml", options={}
)
def generate_diff(self, mod_dir: Path, modded_files: List[Union[Path, str]]):
if (
f"{util.get_content_path()}/Pack/TitleBG.pack//Quest/QuestProduct.sbquestpack"
not in modded_files
):
return {}
print("Logging modified quests...")
stock_quests = get_stock_quests()
stock_names = [q["Name"] for q in stock_quests]
def update_mod(self, params):
try:
update_file = self.file_pick({"multiple": False})[0]
except IndexError:
return
mod = BcmlMod.from_json(params["mod"])
if (mod.path / "options.json").exists():
options = json.loads(
(mod.path / "options.json").read_text(), encoding="utf-8"
)
else:
options = {}
remergers = mergers.get_mergers_for_mod(mod)
rmtree(mod.path)
with Pool(maxtasksperchild=1000) as pool:
new_mod = install.install_mod(
Path(update_file),
insert_priority=mod.priority,
options=options,
pool=pool,
)
remergers |= {
m
for m in mergers.get_mergers_for_mod(new_mod)
if m.NAME not in {m.NAME for m in remergers}
}
try:
install.refresh_merges()
except Exception: # pylint: disable=broad-except
if not options:
options = {
'disable': [],
'options': {}
}
if 'disable' not in options:
options['disable'] = []
pool = original_pool or Pool(cpu_count())
print('Scanning for modified files...')
modded_files = find_modded_files(tmp_dir, verbose=verbose, original_pool=original_pool)
if not modded_files:
raise RuntimeError('No modified files were found. Very unusual.')
(tmp_dir / 'logs').mkdir(parents=True, exist_ok=True)
for merger_class in [merger_class for merger_class in mergers.get_mergers() \
if merger_class.NAME not in options['disable']]:
merger = merger_class()
merger.set_pool(pool)
if options is not None and merger.NAME in options:
merger.set_options(options[merger.NAME])
merger.log_diff(tmp_dir, modded_files)
if not original_pool:
pool.close()
pool.join()
return modded_files
from typing import List, Union
import oead
import rstb
from bcml import util, mergers
from bcml.mergers import rstable
def get_stock_effects() -> oead.byml.Hash:
bootup_sarc = oead.Sarc(util.get_game_file("Pack/Bootup.pack").read_bytes())
return oead.byml.from_binary(
util.decompress(bootup_sarc.get_file("Ecosystem/StatusEffectList.sbyml").data)
)[0]
class StatusEffectMerger(mergers.Merger):
NAME: str = "effects"
def __init__(self):
super().__init__(
"status effect",
"Merges changes to StatusEffectList.byml",
"effects.yml",
options={},
)
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]):
needle = f"{util.get_content_path()}/Pack/Bootup.pack//Ecosystem/StatusEffectList.sbyml"
if needle not in modded_files:
return {}
print("Logging changes to effect status levels...")
stock_effects = get_stock_effects()
@staticmethod
def is_bootup_injector():
return True
def get_bootup_injection(self):
tmp_sarc = util.get_master_modpack_dir() / 'logs' / 'savedata.sarc'
if tmp_sarc.exists():
return (
'GameData/savedataformat.ssarc',
util.compress(tmp_sarc.read_bytes())
)
else:
return
class ActorInfoMerger(mergers.Merger):
NAME: str = 'actors'
def __init__(self):
super().__init__('actor info merge', 'Merges changes to ActorInfo.product.byml',
'actorinfo.yml ', {})
def generate_diff(self, mod_dir: Path, modded_files: List[Union[Path, str]]):
try:
actor_file = next(iter([file for file in modded_files \
if Path(file).name == 'ActorInfo.product.sbyml']))
except StopIteration:
return {}
actor_info = byml.Byml(util.decompress_file(str(actor_file))).parse()
print('Detecting modified actor info entries...')
return get_modded_actors(actor_info)
def resort_mods(self):
all_mergers = [merger() for merger in mergers.get_mergers()]
remergers = set()
partials = {}
mods_to_change = []
for i in range(self.listWidget.count()):
mod = self.listWidget.item(i).data(QtCore.Qt.UserRole)
target_priority = i + 100 if util.get_settings_bool(
'load_reverse') else 100 + ((self.listWidget.count() - 1) - i)
if mod.priority != target_priority:
mods_to_change.append(BcmlMod(mod.name, target_priority, mod.path))
for merger in all_mergers:
if merger.is_mod_logged(mod):
remergers.add(merger)
if merger.can_partial_remerge():
if merger.NAME not in partials:
partials[merger.NAME] = set()
partials[merger.NAME] |= set(merger.get_mod_affected(mod))
rstb_path = util.get_modpack_dir() / '9999_BCML' / 'content' / 'System' / 'Resource' /\
'ResourceSizeTable.product.srsizetable'
if rstb_path.exists():
table: rstb.ResourceSizeTable = rstb.util.read_rstb(
str(rstb_path), True)
else:
table = rstable.get_stock_rstb()
msg_path = f'Message/Msg_{lang}.product.sarc'
if table.is_in_table(msg_path):
print('Correcting RSTB...')
table.delete_entry(msg_path)
rstb_path.parent.mkdir(parents=True, exist_ok=True)
rstb.util.write_rstb(table, str(rstb_path), True)
class TextsMerger(mergers.Merger):
""" A merger for game texts """
NAME: str = 'texts'
def __init__(self, user_only: bool = True):
super().__init__('texts merge', 'Merges changes to game texts', '', options={
'user_only': user_only
})
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]):
diffs = {}
bootups = {util.get_file_language(file): file for file in modded_files
if 'Bootup_' in str(file) and 'Graphics' not in str(file)
and isinstance(file, Path)}
if not bootups:
return {}
mod_langs = list(bootups.keys())
break
if "Bootup.pack" in file_name:
for merger in [
merger() for merger in mergers.get_mergers() if merger.is_bootup_injector()
]:
inject = merger.get_bootup_injection()
if not inject:
continue
file, data = inject
new_sarc.files[file] = data
return (file_name, bytes(new_sarc.write()[1]))
class PackMerger(mergers.Merger):
""" A merger for modified pack files """
NAME: str = "packs"
def __init__(self):
super().__init__("packs", "Merges modified files within SARCs", "packs.json", {})
def can_partial_remerge(self):
return True
def get_mod_affected(self, mod):
return self.get_mod_diff(mod)
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]):
print("Finding modified SARCs...")
packs = {}