def merge_events(): """ Merges all installed event info mods """ event_mods = [mod for mod in util.get_installed_mods() \ if (mod.path / 'logs' / 'eventinfo.yml').exists()] merged_events = util.get_master_modpack_dir() / 'logs' / 'eventinfo.byml' event_merge_log = util.get_master_modpack_dir() / 'logs' / 'eventinfo.log' event_mod_hash = str(hash(tuple(event_mods))) if not event_mods: print('No event info merging necessary') if merged_events.exists(): merged_events.unlink() event_merge_log.unlink() try: stock_eventinfo = util.get_nested_file_bytes( str(util.get_game_file('Pack/Bootup.pack')) + '//Event/EventInfo.product.sbyml', unyaz=False ) util.inject_file_into_bootup( 'Event/EventInfo.product.sbyml', stock_eventinfo ) except FileNotFoundError: pass return if event_merge_log.exists() and event_merge_log.read_text() == event_mod_hash: print('No event info merging necessary') return print('Loading event info mods...') modded_events = {} for mod in event_mods: modded_events.update(get_events_for_mod(mod)) new_events = get_stock_eventinfo() for event, data in modded_events.items(): new_events[event] = data print('Writing new event info...') event_bytes = byml.Writer(new_events, be=True).get_bytes() util.inject_file_into_bootup( 'Event/EventInfo.product.sbyml', util.compress(event_bytes), create_bootup=True ) print('Saving event info merge log...') event_merge_log.write_text(event_mod_hash) merged_events.write_bytes(event_bytes) print('Updating RSTB...') rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext(event_bytes, True, '.byml') rstable.set_size('Event/EventInfo.product.byml', rstb_size)
def merge_savedata(verbose: bool = False, force: bool = False): """ Merges install savedata mods and saves the new Bootup.pack, fixing the RSTB if needed""" mods = get_savedata_mods() slog_path = util.get_master_modpack_dir() / 'logs' / 'savedata.log' if not mods: print('No gamedata merging necessary.') if slog_path.exists(): slog_path.unlink() if (util.get_master_modpack_dir() / 'logs' / 'savedata.sarc').exists(): (util.get_master_modpack_dir() / 'logs' / 'savedata.sarc').unlink() return if slog_path.exists() and not force: with slog_path.open('r') as l_file: if xxhash.xxh32(str(mods)).hexdigest() == l_file.read(): print('No savedata merging necessary.') return new_entries = [] new_hashes = [] loader = yaml.CSafeLoader yaml_util.add_constructors(loader) print('Loading savedata mods...') for mod in mods: with open(mod.path / 'logs' / 'savedata.yml') as s_file: yml = yaml.load(s_file, Loader=loader) for entry in yml: if entry['HashValue'] in new_hashes: continue else: new_entries.append(entry) new_hashes.append(entry['HashValue']) if verbose: print(f' Added {entry["DataName"]} from {mod.name}') savedata = get_stock_savedata() merged_entries = [] save_files = sorted(savedata.list_files())[0:-2] print('Loading stock savedata...') for file in save_files: merged_entries.extend(byml.Byml(savedata.get_file_data( file).tobytes()).parse()['file_list'][1]) print('Merging changes...') merged_entries.extend(new_entries) merged_entries.sort(key=lambda x: x['HashValue']) special_bgsv = [ savedata.get_file_data('/saveformat_6.bgsvdata').tobytes(), savedata.get_file_data('/saveformat_7.bgsvdata').tobytes(), ] print('Creating and injecting new savedataformat.sarc...') new_savedata = sarc.SARCWriter(True) num_files = ceil(len(merged_entries) / 8192) for i in range(num_files): end_pos = (i+1) * 8192 if end_pos > len(merged_entries): end_pos = len(merged_entries) buf = BytesIO() byml.Writer({ 'file_list': [ { 'IsCommon': False, 'IsCommonAtSameAccount': False, 'IsSaveSecureCode': True, 'file_name': 'game_data.sav' }, merged_entries[i*8192:end_pos] ], 'save_info': [ { 'directory_num': byml.Int(8), 'is_build_machine': True, 'revision': byml.Int(18203) } ] }, True).write(buf) new_savedata.add_file(f'/saveformat_{i}.bgsvdata', buf.getvalue()) new_savedata.add_file(f'/saveformat_{num_files}.bgsvdata', special_bgsv[0]) new_savedata.add_file( f'/saveformat_{num_files + 1}.bgsvdata', special_bgsv[1]) bootup_rstb = inject_savedata_into_bootup(new_savedata) (util.get_master_modpack_dir() / 'logs').mkdir(parents=True, exist_ok=True) with (util.get_master_modpack_dir() / 'logs' / 'savedata.sarc').open('wb') as s_file: new_savedata.write(s_file) print('Updating RSTB...') rstable.set_size('GameData/savedataformat.sarc', bootup_rstb) slog_path.parent.mkdir(parents=True, exist_ok=True) with slog_path.open('w', encoding='utf-8') as l_file: l_file.write(xxhash.xxh32(str(mods)).hexdigest())
def merge_gamedata(verbose: bool = False, force: bool = False): """ Merges installed gamedata mods and saves the new Bootup.pack, fixing the RSTB if needed """ mods = get_gamedata_mods() glog_path = util.get_master_modpack_dir() / 'logs' / 'gamedata.log' if not mods: print('No gamedata merging necessary.') if glog_path.exists(): glog_path.unlink() if (util.get_master_modpack_dir() / 'logs' / 'gamedata.sarc').exists(): (util.get_master_modpack_dir() / 'logs' / 'gamedata.sarc').unlink() return if glog_path.exists() and not force: with glog_path.open('r') as l_file: if xxhash.xxh32(str(mods)).hexdigest() == l_file.read(): print('No gamedata merging necessary.') return modded_entries = {} loader = yaml.CSafeLoader yaml_util.add_constructors(loader) print('Loading gamedata mods...') for mod in mods: with (mod.path / 'logs' / 'gamedata.yml').open('r') as g_file: yml = yaml.load(g_file, Loader=loader) for data_type in yml: if data_type not in modded_entries: modded_entries[data_type] = {} modded_entries[data_type].update(yml[data_type]) if verbose: print(f' Added entries for {data_type} from {mod.name}') gamedata = get_stock_gamedata() merged_entries = {} print('Loading stock gamedata...') for yml in gamedata.list_files(): base_yml = byml.Byml(gamedata.get_file_data(yml).tobytes()).parse() for data_type in base_yml: if data_type not in merged_entries: merged_entries[data_type] = [] merged_entries[data_type].extend(base_yml[data_type]) print('Merging changes...') for data_type in merged_entries: if data_type in modded_entries: for entry in [entry for entry in merged_entries[data_type] if entry['DataName'] in modded_entries[data_type]]: i = merged_entries[data_type].index(entry) if verbose: print(f' {entry["DataName"]} has been modified') merged_entries[data_type][i] = deepcopy( modded_entries[data_type][entry['DataName']]) print(f'Merged modified {data_type} entries') for data_type in modded_entries: for entry in [entry for entry in modded_entries[data_type] if entry not in [entry['DataName'] for entry in merged_entries[data_type]]]: if verbose: print(f' {entry} has been added') merged_entries[data_type].append(modded_entries[data_type][entry]) print(f'Merged new {data_type} entries') print('Creating and injecting new gamedata.sarc...') new_gamedata = sarc.SARCWriter(True) for data_type in merged_entries: num_files = ceil(len(merged_entries[data_type]) / 4096) for i in range(num_files): end_pos = (i+1) * 4096 if end_pos > len(merged_entries[data_type]): end_pos = len(merged_entries[data_type]) buf = BytesIO() byml.Writer( {data_type: merged_entries[data_type][i*4096:end_pos]}, be=True).write(buf) new_gamedata.add_file(f'/{data_type}_{i}.bgdata', buf.getvalue()) bootup_rstb = inject_gamedata_into_bootup(new_gamedata) (util.get_master_modpack_dir() / 'logs').mkdir(parents=True, exist_ok=True) with (util.get_master_modpack_dir() / 'logs' / 'gamedata.sarc').open('wb') as g_file: new_gamedata.write(g_file) print('Updating RSTB...') rstable.set_size('GameData/gamedata.sarc', bootup_rstb) glog_path.parent.mkdir(parents=True, exist_ok=True) with glog_path.open('w', encoding='utf-8') as l_file: l_file.write(xxhash.xxh32(str(mods)).hexdigest())