Example #1
0
    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
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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))
Example #6
0
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
Example #7
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)