def clear_vpk_files(game) -> str: """Remove existing VPKs files from a game. We want to leave other files - otherwise users will end up regenerating the sound cache every time they export. This returns the path to the game folder. """ dest_folder = game.abs_path( VPK_FOLDER.get( game.steamID, 'portal2_dlc3', )) os.makedirs(dest_folder, exist_ok=True) try: for file in os.listdir(dest_folder): if file[:6] == 'pak01_': os.remove(os.path.join(dest_folder, file)) except PermissionError: # The player might have Portal 2 open. Abort changing the VPK. LOGGER.warning("Couldn't replace VPK files. Is Portal 2 " "or Hammer open?") raise return dest_folder
def post_parse(cls) -> None: """Verify no quote packs have duplicate IDs.""" def iter_lines(conf: Property) -> Iterator[Property]: """Iterate over the varios line blocks.""" yield from conf.find_all("Quotes", "Group", "Quote", "Line") yield from conf.find_all("Quotes", "Midchamber", "Quote", "Line") for group in conf.find_children("Quotes", "CoopResponses"): if group.has_children(): yield from group for voice in cls.all(): used: Set[str] = set() for quote in iter_lines(voice.config): try: quote_id = quote['id'] except LookupError: quote_id = quote['name', ''] LOGGER.warning( 'Quote Pack "{}" has no specific ID for "{}"!', voice.id, quote_id, ) if quote_id in used: LOGGER.warning( 'Quote Pack "{}" has duplicate ' 'voice ID "{}"!', voice.id, quote_id, ) used.add(quote_id)
def export(exp_data: ExportData) -> None: """Export all the packlists.""" pack_block = Property('PackList', []) for pack in PackList.all(): # type: PackList # Build a # "Pack_id" # { # "File" "filename" # "File" "filename" # } # block for each packlist files = [Property('File', file) for file in pack.files] pack_block.append(Property( pack.id, files, )) LOGGER.info('Writing packing list!') with open(exp_data.game.abs_path('bin/bee2/pack_list.cfg'), 'w') as pack_file: for line in pack_block.export(): pack_file.write(line)
def export(exp_data: ExportData) -> None: """Export the quotepack.""" if exp_data.selected is None: return # No quote pack! try: voice = QuotePack.by_id(exp_data.selected) # type: QuotePack except KeyError: raise Exception("Selected voice ({}) doesn't exist?".format( exp_data.selected)) from None vbsp_config = exp_data.vbsp_conf # type: Property # We want to strip 'trans' sections from the voice pack, since # they're not useful. for prop in voice.config: if prop.name == 'quotes': vbsp_config.append(QuotePack.strip_quote_data(prop)) else: vbsp_config.append(prop.copy()) # Set values in vbsp_config, so flags can determine which voiceline # is selected. options = vbsp_config.ensure_exists('Options') options['voice_pack'] = voice.id options['voice_char'] = ','.join(voice.chars) if voice.cave_skin is not None: options['cave_port_skin'] = str(voice.cave_skin) if voice.studio is not None: options['voice_studio_inst'] = voice.studio options['voice_studio_actor'] = voice.studio_actor options['voice_studio_inter_chance'] = str(voice.inter_chance) options['voice_studio_cam_loc'] = voice.cam_loc.join(' ') options['voice_studio_cam_pitch'] = str(voice.cam_pitch) options['voice_studio_cam_yaw'] = str(voice.cam_yaw) options['voice_studio_should_shoot'] = srctools.bool_as_int( voice.turret_hate) # Copy the config files for this voiceline.. for prefix, pretty in [('', 'normal'), ('mid_', 'MidChamber'), ('resp_', 'Responses')]: path = utils.conf_location('config/voice/') / (prefix.upper() + voice.id + '.cfg') LOGGER.info('Voice conf path: {}', path) if path.is_file(): shutil.copy( str(path), exp_data.game.abs_path( 'bin/bee2/{}voice.cfg'.format(prefix))) LOGGER.info('Written "{}voice.cfg"', prefix) else: LOGGER.info('No {} voice config!', pretty)
def export(exp_data: ExportData): """Generate the VPK file in the game folder.""" sel_vpk = exp_data.selected_style.vpk_name if sel_vpk: for vpk in StyleVPK.all(): if vpk.id.casefold() == sel_vpk: sel_vpk = vpk break else: sel_vpk = None else: sel_vpk = None try: dest_folder = StyleVPK.clear_vpk_files(exp_data.game) except PermissionError: raise NoVPKExport() # We can't edit the VPK files - P2 is open.. if exp_data.game.steamID == utils.STEAM_IDS['PORTAL2']: # In Portal 2, we make a dlc3 folder - this changes priorities, # so the soundcache will be regenerated. Just copy the old one over. sound_cache = os.path.join(dest_folder, 'maps', 'soundcache', '_master.cache') LOGGER.info('Sound cache: {}', sound_cache) if not os.path.isfile(sound_cache): LOGGER.info('Copying over soundcache file for DLC3..') os.makedirs(os.path.dirname(sound_cache), exist_ok=True) try: shutil.copy( exp_data.game.abs_path( 'portal2_dlc2/maps/soundcache/_master.cache', ), sound_cache, ) except FileNotFoundError: # It's fine, this will be regenerated automatically pass # Generate the VPK. vpk_file = VPK(os.path.join(dest_folder, 'pak01_dir.vpk'), mode='w') with vpk_file: if sel_vpk is not None: for file in sel_vpk.fsys.walk_folder(sel_vpk.dir): with file.open_bin() as open_file: vpk_file.add_file( file.path, open_file.read(), sel_vpk.dir, ) # Additionally, pack in game/vpk_override/ into the vpk - this allows # users to easily override resources in general. override_folder = exp_data.game.abs_path('vpk_override') os.makedirs(override_folder, exist_ok=True) # Also write a file to explain what it's for.. with open(os.path.join(override_folder, 'BEE2_README.txt'), 'w') as f: f.write(VPK_OVERRIDE_README) vpk_file.add_folder(override_folder) del vpk_file[ 'BEE2_README.txt'] # Don't add this to the VPK though.. LOGGER.info('Written {} files to VPK!', len(vpk_file))
async def main(files: List[str]) -> int: """Run the transfer.""" if not files: LOGGER.error('No files to copy!') LOGGER.error('packages_sync: {}', __doc__) return 1 try: portal2_loc = Path(os.environ['PORTAL_2_LOC']) except KeyError: raise ValueError( 'Environment Variable $PORTAL_2_LOC not set! ' 'This should be set to Portal 2\'s directory.' ) from None # Load the general options in to find out where packages are. GEN_OPTS.load() # Borrow PackageLoader to do the finding and loading for us. LOGGER.info('Locating packages...') # Disable logging of package info. packages_logger.setLevel(logging.ERROR) async with trio.open_nursery() as nursery: for loc in get_package_locs(): await find_packages(nursery, loc) packages_logger.setLevel(logging.INFO) LOGGER.info('Done!') print_package_ids() package_loc = Path('../', GEN_OPTS['Directories']['package']).resolve() file_list: list[Path] = [] for file in files: file_path = Path(file) if file_path.is_dir(): for sub_file in file_path.glob('**/*'): if sub_file.is_file(): file_list.append(sub_file) else: file_list.append(file_path) files_to_check = set() for file_path in file_list: if file_path.suffix.casefold() in {'.vmx', '.log', '.bsp', '.prt', '.lin'}: # Ignore these file types. continue files_to_check.add(file_path) if file_path.suffix == '.mdl': for suffix in ['.vvd', '.phy', '.dx90.vtx', '.sw.vtx']: sub_file = file_path.with_suffix(suffix) if sub_file.exists(): files_to_check.add(sub_file) LOGGER.info('Processing {} files...', len(files_to_check)) for file_path in files_to_check: check_file(file_path, portal2_loc, package_loc) if SKIPPED_FILES: LOGGER.warning('Skipped missing files:') for file in SKIPPED_FILES: LOGGER.info('- {}', file) return 0
def parse(cls, data: ParseData) -> 'PackList': """Read pack lists from packages.""" filesystem = data.fsys conf = data.info.find_key('Config', '') if 'AddIfMat' in data.info: LOGGER.warning( '{}:{}: AddIfMat is no ' 'longer used.', data.pak_id, data.id, ) files = [] if conf.has_children(): # Allow having a child block to define packlists inline files = [prop.value for prop in conf] elif conf.value: path = 'pack/' + conf.value + '.cfg' with filesystem, filesystem.open_str(path) as f: # Each line is a file to pack. # Skip blank lines, strip whitespace, and # allow // comments. for line in f: line = srctools.clean_line(line) if line: files.append(line) # Deprecated old option. for prop in data.info.find_all('AddIfMat'): files.append('materials/' + prop.value + '.vmt') if not files: raise ValueError('"{}" has no files to pack!'.format(data.id)) if CHECK_PACKFILE_CORRECTNESS: # Use normpath so sep differences are ignored, plus case. resources = { os.path.normpath(file.path).casefold() for file in filesystem.walk_folder('resources/') } for file in files: if file.startswith(('-#', 'precache_sound:')): # Used to disable stock soundscripts, and precache sounds # Not to pack - ignore. continue file = file.lstrip( '#') # This means to put in soundscript too... # Check to make sure the files exist... file = os.path.join('resources', os.path.normpath(file)).casefold() if file not in resources: LOGGER.warning( 'Warning: "{file}" not in zip! ({pak_id})', file=file, pak_id=data.pak_id, ) return cls(data.id, files)