Beispiel #1
0
    def test_simple(self):
        pairs = [
            ('a', 'test'),
            ('a2', b'\xff\xfe0\x041\x042\x043\x04'.decode('utf-16')),
            ('bb', 1),
            ('bb2', -500),
            ('ccc', 1.0),
            ('dddd', vdf.POINTER(1234)),
            ('fffff', vdf.COLOR(1234)),
            ('gggggg', vdf.UINT_64(1234)),
            ('hhhhhhh', vdf.INT_64(-1234)),
        ]

        data = OrderedDict(pairs)
        data['level1-1'] = OrderedDict(pairs)
        data['level1-1']['level2-1'] = OrderedDict(pairs)
        data['level1-1']['level2-2'] = OrderedDict(pairs)
        data['level1-2'] = OrderedDict(pairs)

        result = vdf.binary_loads(vdf.binary_dumps(data), mapper=OrderedDict)

        self.assertEqual(data, result)

        result = vdf.binary_loads(vdf.binary_dumps(data, alt_format=True),
                                  mapper=OrderedDict,
                                  alt_format=True)

        self.assertEqual(data, result)

        result = vdf.vbkv_loads(vdf.vbkv_dumps(data), mapper=OrderedDict)

        self.assertEqual(data, result)
Beispiel #2
0
    def test_simple(self):
        pairs = [
            ('a', 'test'),
            ('a2', b'\xd0\xb0\xd0\xb1\xd0\xb2\xd0\xb3'.decode('utf-8')),
            ('bb', 1),
            ('bb2', -500),
            ('ccc', 1.0),
            ('dddd', vdf.POINTER(1234)),
            ('fffff', vdf.COLOR(1234)),
            ('gggggg', vdf.UINT_64(1234)),
            ('hhhhhhh', vdf.INT_64(-1234)),
        ]

        data = OrderedDict(pairs)
        data['level1-1'] = OrderedDict(pairs)
        data['level1-1']['level2-1'] = OrderedDict(pairs)
        data['level1-1']['level2-2'] = OrderedDict(pairs)
        data['level1-2'] = OrderedDict(pairs)

        result = vdf.binary_loads(vdf.binary_dumps(data), mapper=OrderedDict)

        self.assertEqual(data, result)

        result = vdf.binary_loads(vdf.binary_dumps(data, alt_format=True), mapper=OrderedDict, alt_format=True)

        self.assertEqual(data, result)

        result = vdf.vbkv_loads(vdf.vbkv_dumps(data), mapper=OrderedDict)

        self.assertEqual(data, result)
Beispiel #3
0
    def test_raise_on_remaining(self):
        # default binary_loads is to raise
        with self.assertRaises(SyntaxError):
            vdf.binary_loads(b'\x01key\x00value\x00\x08' + b'aaaa')

        # do not raise
        self.assertEqual(
            vdf.binary_loads(b'\x01key\x00value\x00\x08' + b'aaaa',
                             raise_on_remaining=False), {'key': 'value'})
Beispiel #4
0
    def test_merge_multiple_keys_off(self):
        # VDFDict([('a', VDFDict([('a', '1'), ('b', '2')])), ('a', VDFDict([('a', '3'), ('c', '4')]))])
        test = b'\x00a\x00\x01a\x001\x00\x01b\x002\x00\x08\x00a\x00\x01a\x003\x00\x01c\x004\x00\x08\x08'
        result = {'a': {'a': '3', 'c': '4'}}

        self.assertEqual(vdf.binary_loads(test, merge_duplicate_keys=False),
                         result)
Beispiel #5
0
def appinfo_loads(data):

    # These should always be present.
    version, universe = struct.unpack_from("<II",data,0)
    offset = 8
    if version != 0x07564427 and universe != 1:
        raise ValueError("Invalid appinfo header")
    result = {}
    # Parsing applications
    app_fields = namedtuple("App",'size state last_update access_token checksum change_number')
    app_struct = struct.Struct("<3IQ20sI")
    while True:
        app_id = struct.unpack_from('<I',data,offset)[0]
        offset += 4

        # AppID = 0 marks the last application in the Appinfo
        if app_id == 0:
            break

        # All fields are required.
        app_info = app_fields._make(app_struct.unpack_from(data,offset))

        offset += app_struct.size
        size = app_info.size + 4 - app_struct.size
        app = vdf.binary_loads(data[offset:offset+size])
        offset += size

        result[app_id] = app['appinfo']

    return result
Beispiel #6
0
    async def _process_package_info_response(self, body):
        logger.info("Processing message PICSProductInfoResponse")
        message = steammessages_clientserver_pb2.CMsgClientPICSProductInfoResponse()
        message.ParseFromString(body)
        apps_to_parse = []

        for info in message.packages:
            await self.package_info_handler(str(info.packageid))
            if info.packageid == 0:
                # Packageid 0 contains trash entries for every user
                logger.info("Skipping packageid 0 ")
                continue
            package_content = vdf.binary_loads(info.buffer[4:])
            for app in package_content[str(info.packageid)]['appids']:
                await self.app_info_handler(mother_appid=str(info.packageid), appid=str(package_content[str(info.packageid)]['appids'][app]))
                apps_to_parse.append(package_content[str(info.packageid)]['appids'][app])

        for info in message.apps:
            app_content = vdf.loads(info.buffer[:-1].decode('utf-8', 'replace'))
            try:
                if app_content['appinfo']['common']['type'].lower() == 'game':
                    logger.info(f"Retrieved game {app_content['appinfo']['common']['name']}")
                    await self.app_info_handler(appid=str(app_content['appinfo']['appid']), title=app_content['appinfo']['common']['name'], game=True)
                else:
                    await self.app_info_handler(appid=str(app_content['appinfo']['appid']), game=False)
            except KeyError:
                # Unrecognized app type
                await self.app_info_handler(appid=str(app_content['appinfo']['appid']), game=False)

        if len(apps_to_parse) > 0:
            logger.info(f"Apps to parse {apps_to_parse}, {len(apps_to_parse)} entries")
            await self.get_apps_info(apps_to_parse)
Beispiel #7
0
    def get_product_info(self, appids=[], packageids=[]):
        try:
            resp = self.steam.send_job_and_wait(MsgProto(EMsg.ClientPICSProductInfoRequest),
                                                {
                'apps': map(lambda x: {'appid': x}, appids),
                'packages': map(lambda x: {'packageid': x}, packageids),
            },
                timeout=10
            )

            if not resp:
                return {}

            resp = proto_to_dict(resp)

            for app in resp.get('apps', []):
                app['appinfo'] = vdf.loads(
                    app.pop('buffer')[:-1].decode('utf-8', 'replace'))['appinfo']
                app['sha'] = hexlify(app['sha']).decode('utf-8')
            for pkg in resp.get('packages', []):
                pkg['appinfo'] = vdf.binary_loads(pkg.pop('buffer')[4:])[
                    str(pkg['packageid'])]
                pkg['sha'] = hexlify(pkg['sha']).decode('utf-8')

            return resp
        except Exception as e:
            print('get_product_info() exception: ' + str(e))
            return {}
Beispiel #8
0
    def get_change_number(self, appids=None):
        if not appids:
            appids = []
        else:
            appids = [appids]

        packageids = []

        resp = self.steam.send_job_and_wait(
            MsgProto(EMsg.ClientPICSProductInfoRequest), {
                'apps': map(lambda x: {'appid': x}, appids),
                'packages': map(lambda x: {'packageid': x}, packageids),
            },
            timeout=10)

        if not resp:
            return {}

        resp = proto_to_dict(resp)

        for app in resp.get('apps', []):
            app['appinfo'] = vdf.loads(
                app.pop('buffer').rstrip('\x00'))['appinfo']
            app['sha'] = hexlify(app['sha'])
        for pkg in resp.get('packages', []):
            pkg['appinfo'] = vdf.binary_loads(pkg.pop('buffer')[4:])[str(
                pkg['packageid'])]
            pkg['sha'] = hexlify(pkg['sha'])

        return resp['apps'][0]['change_number']
Beispiel #9
0
    def get_product_info(self, apps=[], packages=[], timeout=15):
        """Get product info for apps and packages

        :param apps: items in the list should be either just ``app_id``, or ``(app_id, access_token)``
        :type apps: :class:`list`
        :param packages: items in the list should be either just ``package_id``, or ``(package_id, access_token)``
        :type packages: :class:`list`
        :return: dict with ``apps`` and ``packages`` containing their info, see example below
        :rtype: :class:`dict`, :class:`None`

        .. code:: python

            {'apps':     {570: {...}, ...},
             'packages': {123: {...}, ...}
            }
        """
        if not apps and not packages: return

        message = MsgProto(EMsg.ClientPICSProductInfoRequest)

        for app in apps:
            app_info = message.body.apps.add()
            app_info.only_public = False
            if isinstance(app, tuple):
                app_info.appid, app_info.access_token = app
            else:
                app_info.appid = app

        for package in packages:
            package_info = message.body.packages.add()
            if isinstance(package, tuple):
                package_info.appid, package_info.access_token = package
            else:
                package_info.packageid = package

        message.body.meta_data_only = False

        job_id = self.send_job(message)

        data = dict(apps={}, packages={})

        while True:
            chunk = self.wait_event(job_id, timeout=timeout)

            if chunk is None: return
            chunk = chunk[0].body

            for app in chunk.apps:
                data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode(
                    'utf-8', 'replace'))['appinfo']
            for pkg in chunk.packages:
                data['packages'][pkg.packageid] = vdf.binary_loads(
                    pkg.buffer[4:])[str(pkg.packageid)]

            if not chunk.response_pending:
                break

        return data
Beispiel #10
0
    def get_product_info(self, apps=[], packages=[], timeout=15):
        """Get product info for apps and packages

        :param apps: items in the list should be either just ``app_id``, or ``(app_id, access_token)``
        :type apps: :class:`list`
        :param packages: items in the list should be either just ``package_id``, or ``(package_id, access_token)``
        :type packages: :class:`list`
        :return: dict with ``apps`` and ``packages`` containing their info, see example below
        :rtype: :class:`dict`, :class:`None`

        .. code:: python

            {'apps':     {570: {...}, ...},
             'packages': {123: {...}, ...}
            }
        """
        if not apps and not packages: return

        message = MsgProto(EMsg.ClientPICSProductInfoRequest)

        for app in apps:
                app_info = message.body.apps.add()
                app_info.only_public = False
                if isinstance(app, tuple):
                        app_info.appid, app_info.access_token = app
                else:
                        app_info.appid = app

        for package in packages:
                package_info = message.body.packages.add()
                if isinstance(package, tuple):
                        package_info.appid, package_info.access_token = package
                else:
                        package_info.packageid = package

        message.body.meta_data_only = False

        job_id = self.send_job(message)

        data = dict(apps={}, packages={})

        while True:
            chunk = self.wait_event(job_id, timeout=timeout)

            if chunk is None: return
            chunk = chunk[0].body

            for app in chunk.apps:
                data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode('utf-8', 'replace'))['appinfo']
            for pkg in chunk.packages:
                data['packages'][pkg.packageid] = vdf.binary_loads(pkg.buffer[4:])[str(pkg.packageid)]

            if not chunk.response_pending:
                break

        return data
Beispiel #11
0
def get_appinfo_sections(path):
    """
    Parse an appinfo.vdf file and return all the deserialized binary VDF
    objects inside it
    """
    # appinfo.vdf is not actually a (binary) VDF file, but a binary file
    # containing multiple binary VDF sections.
    # File structure based on comment from vdf developer:
    # https://github.com/ValvePython/vdf/issues/13#issuecomment-321700244
    with open(path, "rb") as f:
        data = f.read()
        i = 0

        # Parse the header
        header_size = struct.calcsize(APPINFO_STRUCT_HEADER)
        magic, universe = struct.unpack(
            APPINFO_STRUCT_HEADER, data[0:header_size]
        )

        i += header_size

        if magic != b"'DV\x07":
            raise SyntaxError("Invalid file magic number")

        sections = []

        section_size = struct.calcsize(APPINFO_STRUCT_SECTION)
        while True:
            # We don't need any of the fields besides 'entry_size',
            # which is used to determine the length of the variable-length VDF
            # field.
            # Still, here they are for posterity's sake.
            (appid, entry_size, infostate, last_updated, access_token,
             sha_hash, change_number) = struct.unpack(
                APPINFO_STRUCT_SECTION, data[i:i+section_size])
            vdf_section_size = entry_size - 40

            i += section_size
            try:
                vdf_d = vdf.binary_loads(data[i:i+vdf_section_size])
                sections.append(vdf_d)
            except UnicodeDecodeError:
                # vdf is unable to decode binary VDF objects containing
                # invalid UTF-8 strings.
                # Since we're only interested in the SteamPlay manifests,
                # we can skip those faulty sections.
                #
                # TODO: Remove this once the upstream bug at
                # https://github.com/ValvePython/vdf/issues/20
                # is fixed
                pass

            i += vdf_section_size

            if i == len(data) - 4:
                return sections
Beispiel #12
0
def get_custom_windows_shortcuts(steam_path):
    """
    Get a list of custom shortcuts for Windows applications as a list
    of SteamApp objects
    """
    # Get the Steam ID3 for the currently logged-in user
    steamid3 = find_current_steamid3(steam_path)

    shortcuts_path = os.path.join(
        steam_path, "userdata", str(steamid3), "config", "shortcuts.vdf"
    )

    try:
        with open(shortcuts_path, "rb") as f:
            content = f.read()
            vdf_data = vdf.binary_loads(content)
    except IOError:
        logger.info(
            "Couldn't find custom shortcuts. Maybe none have been created yet?"
        )
        return []

    steam_apps = []

    for shortcut_id, shortcut_data in vdf_data["shortcuts"].items():
        # The "exe" field can also be "Exe". Account for this by making
        # all field names lowercase
        shortcut_data = {k.lower(): v for k, v in shortcut_data.items()}
        shortcut_id = int(shortcut_id)

        appid = get_appid_from_shortcut(
            target=shortcut_data["exe"], name=shortcut_data["appname"]
        )

        prefix_path = os.path.join(
            steam_path, "steamapps", "compatdata", str(appid), "pfx"
        )
        install_path = shortcut_data["startdir"].strip('"')

        if not os.path.isdir(prefix_path):
            continue

        steam_apps.append(
            SteamApp(
                appid=appid,
                name="Non-Steam shortcut: {}".format(shortcut_data["appname"]),
                prefix_path=prefix_path, install_path=install_path
            )
        )

    logger.info(
        "Found %d Steam shortcuts running under Proton", len(steam_apps)
    )

    return steam_apps
Beispiel #13
0
def get_custom_windows_shortcuts(steam_path):
    """
    Get a list of custom shortcuts for Windows applications as a list
    of SteamApp objects
    """
    # Get the Steam ID3 for the currently logged-in user
    steamid3 = find_current_steamid3(steam_path)

    shortcuts_path = \
        steam_path / "userdata" / str(steamid3) / "config" / "shortcuts.vdf"

    try:
        content = shortcuts_path.read_bytes()
        vdf_data = lower_dict(vdf.binary_loads(content))
    except IOError:
        logger.info(
            "Couldn't find custom shortcuts. Maybe none have been created yet?"
        )
        return []

    steam_apps = []

    for shortcut_id, shortcut_data in vdf_data["shortcuts"].items():
        # The "exe" field can also be "Exe". Account for this by making
        # all field names lowercase
        shortcut_data = lower_dict(shortcut_data)
        shortcut_id = int(shortcut_id)

        if "appid" in shortcut_data:
            appid = shortcut_data["appid"] & 0xffffffff
        else:
            appid = get_appid_from_shortcut(target=shortcut_data["exe"],
                                            name=shortcut_data["appname"])

        prefix_path = \
            steam_path / "steamapps" / "compatdata" / str(appid) / "pfx"
        install_path = Path(shortcut_data["startdir"].strip('"'))

        if not prefix_path.is_dir():
            continue

        steam_apps.append(
            SteamApp(appid=appid,
                     name="Non-Steam shortcut: {}".format(
                         shortcut_data["appname"]),
                     prefix_path=prefix_path,
                     install_path=install_path))

    logger.info(
        "Found %d Steam shortcuts running using Steam compatibility tools",
        len(steam_apps))

    return steam_apps
Beispiel #14
0
    def load(self, data):
        buf, self.memberList = StructReader(data), list()

        (self.steamIdChat, self.steamIdFriend, self.chatRoomType, self.steamIdOwner,
         self.steamIdClan, self.chatFlags, self.enterResponse, self.numMembers
         ) = buf.unpack("<QQIQQ?II")
        self.chatRoomName = buf.read_cstring().decode('utf-8')

        for _ in range(self.numMembers):
            self.memberList.append(vdf.binary_loads(buf.read(64))['MessageObject'])

        self.UNKNOWN1, = buf.unpack("<I")
    def load(self, data):
        buf, self.memberList = StructReader(data), list()

        (self.steamIdChat, self.steamIdFriend, self.chatRoomType,
         self.steamIdOwner, self.steamIdClan, self.chatFlags,
         self.enterResponse, self.numMembers) = buf.unpack("<QQIQQ?II")
        self.chatRoomName = buf.read_cstring().decode('utf-8')

        for _ in range(self.numMembers):
            self.memberList.append(
                vdf.binary_loads(buf.read(64))['MessageObject'])

        self.UNKNOWN1, = buf.unpack("<I")
    async def _process_user_stats_response(self, body):
        logger.debug("Processing message ClientGetUserStatsResponse")
        message = steammessages_clientserver_pb2.CMsgClientGetUserStatsResponse(
        )
        message.ParseFromString(body)
        game_id = message.game_id
        stats = message.stats
        achievs = message.achievement_blocks

        logger.debug(f"Processing user stats response for {message.game_id}")
        achievements_schema = vdf.binary_loads(message.schema,
                                               merge_duplicate_keys=False)
        achievements_unlocked = []

        for achievement_block in achievs:
            achi_block_enum = 32 * (achievement_block.achievement_id - 1)
            for index, unlock_time in enumerate(achievement_block.unlock_time):
                if unlock_time > 0:
                    if str(achievement_block.achievement_id) not in achievements_schema[str(game_id)]['stats'] or \
                            str(index) not in achievements_schema[str(game_id)]['stats'][str(achievement_block.achievement_id)]['bits']:
                        logger.warning("Non existent achievement unlocked")
                        continue
                    try:
                        if 'english' in achievements_schema[str(
                                game_id)]['stats'][str(
                                    achievement_block.achievement_id)]['bits'][
                                        str(index)]['display']['name']:
                            name = achievements_schema[str(game_id)]['stats'][
                                str(achievement_block.achievement_id)]['bits'][
                                    str(index)]['display']['name']['english']
                        else:
                            name = achievements_schema[str(game_id)]['stats'][
                                str(achievement_block.achievement_id)]['bits'][
                                    str(index)]['display']['name']
                        achievements_unlocked.append({
                            'id': achi_block_enum + index,
                            'unlock_time': unlock_time,
                            'name': name
                        })
                    except Exception as e:
                        logger.error(
                            "Unable to parse achievement %d from block %s : %s",
                            index, str(achievement_block.achievement_id),
                            repr(e))
                        logger.info(achievs)
                        logger.info(achievements_schema)
                        logger.info(message.schema)
                        raise UnknownBackendResponse(
                            f"Achievements parser error: {e.__class__}")

        await self.stats_handler(game_id, stats, achievements_unlocked)
Beispiel #17
0
 def test_loads_params_invalid(self):
     with self.assertRaises(TypeError):
         vdf.binary_loads([])
     with self.assertRaises(TypeError):
         vdf.binary_loads(11111)
     with self.assertRaises(TypeError):
         vdf.binary_loads(BytesIO())
     with self.assertRaises(TypeError):
         vdf.binary_load(b'', b'bbbb')
    async def _process_package_info_response(self, body):
        logger.info("Processing message PICSProductInfoResponse")
        message = steammessages_clientserver_pb2.CMsgClientPICSProductInfoResponse(
        )
        message.ParseFromString(body)
        apps_to_parse = []

        for info in message.packages:
            await self.package_info_handler()

            package_id = str(info.packageid)
            package_content = vdf.binary_loads(info.buffer[4:])
            package = package_content.get(package_id)
            if package is None:
                continue

            for app in package['appids'].values():
                appid = str(app)
                await self.app_info_handler(package_id=package_id, appid=appid)
                apps_to_parse.append(app)

        for info in message.apps:
            app_content = vdf.loads(info.buffer[:-1].decode(
                'utf-8', 'replace'))
            appid = str(app_content['appinfo']['appid'])
            try:
                type_ = app_content['appinfo']['common']['type'].lower()
                title = app_content['appinfo']['common']['name']
                parent = None
                if 'extended' in app_content['appinfo'] and type_ == 'dlc':
                    parent = app_content['appinfo']['extended']['dlcforappid']
                    logger.info(f"Retrieved dlc {title} for {parent}")
                if type == 'game':
                    logger.info(f"Retrieved game {title}")
                await self.app_info_handler(appid=appid,
                                            title=title,
                                            type=type_,
                                            parent=parent)
            except KeyError:
                logger.info(f"Unrecognized app structure {app_content}")
                await self.app_info_handler(appid=appid,
                                            title='unknown',
                                            type='unknown',
                                            parent=None)

        if len(apps_to_parse) > 0:
            logger.info(
                f"Apps to parse {apps_to_parse}, {len(apps_to_parse)} entries")
            await self.get_apps_info(apps_to_parse)
Beispiel #19
0
def fetch_party():
    global steam3id
    global oldID
    message = MsgProto(EMsg.ClientRichPresenceRequest)
    message.body.steamid_request.extend([theSteamID])
    message.header.routing_appid=570
    resp = client.send_message_and_wait(message, EMsg.ClientRichPresenceInfo)
    try:
        rp = vdf.binary_loads(resp.rich_presence[0].rich_presence_kv)['RP']
    
        if rp['WatchableGameID'] == oldID or 'param0' not in rp:
            return
    except KeyError:
        return
    
    oldID = rp['WatchableGameID']

    steamIDList = re.findall(r'steam_id: (\d*)', str(rp))
    messageSend = '{}: with '.format(game_modes[rp['param0']])
    
    if len(steamIDList) == 1 or not steamIDList:
        messageSend = '{}: Solo queue'.format(game_modes[rp['param0']])
        sendMessage(messageSend)
        return

    num = 0
    for steamID in steamIDList:
        num += 1
        userName = ''
        if int(steamID) == theSteamID:
            continue

        if steamID in knownUsers:
            userName = knownUsers[steamID]
        else:
            userName = client.get_user(steamID).name
        if len(steamIDList) == 2:
            messageSend += userName
            break

        if num == len(steamIDList):
            messageSend = messageSend[:-2]
            messageSend += ' and {}'.format(userName)
            break

        messageSend += '{}, '.format(userName)

    gevent.spawn(sendMessage, messageSend)
Beispiel #20
0
def get_appinfo_sections(path):
    """
    Parse an appinfo.vdf file and return all the deserialized binary VDF
    objects inside it
    """
    # appinfo.vdf is not actually a (binary) VDF file, but a binary file
    # containing multiple binary VDF sections.
    # File structure based on comment from vdf developer:
    # https://github.com/ValvePython/vdf/issues/13#issuecomment-321700244
    data = path.read_bytes()
    i = 0

    # Parse the header
    header_size = struct.calcsize(APPINFO_STRUCT_HEADER)
    magic, universe = struct.unpack(
        APPINFO_STRUCT_HEADER, data[0:header_size]
    )

    i += header_size

    if magic != b"'DV\x07":
        raise SyntaxError("Invalid file magic number")

    sections = []

    section_size = struct.calcsize(APPINFO_STRUCT_SECTION)
    while True:
        # We don't need any of the fields besides 'entry_size',
        # which is used to determine the length of the variable-length VDF
        # field.
        # Still, here they are for posterity's sake.
        (appid, entry_size, infostate, last_updated, access_token,
         sha_hash, change_number) = struct.unpack(
            APPINFO_STRUCT_SECTION, data[i:i+section_size])
        vdf_section_size = entry_size - 40

        i += section_size
        vdf_d = vdf.binary_loads(data[i:i+vdf_section_size])
        sections.append(vdf_d)

        i += vdf_section_size

        if i == len(data) - 4:
            return sections
Beispiel #21
0
    def register_product_key(self, key):
        """Register/Redeem a CD-Key

        :param key: CD-Key
        :type  key: :class:`str`
        :return: format ``(eresult, result_details, receipt_info)``
        :rtype: :class:`tuple`

        Example ``receipt_info``:

        .. code:: python

            {'BasePrice': 0,
              'CurrencyCode': 0,
              'ErrorHeadline': '',
              'ErrorLinkText': '',
              'ErrorLinkURL': '',
              'ErrorString': '',
              'LineItemCount': 1,
              'PaymentMethod': 1,
              'PurchaseStatus': 1,
              'ResultDetail': 0,
              'Shipping': 0,
              'Tax': 0,
              'TotalDiscount': 0,
              'TransactionID': UINT_64(111111111111111111),
              'TransactionTime': 1473000000,
              'lineitems': {'0': {'ItemDescription': 'Half-Life 3',
                'TransactionID': UINT_64(11111111111111111),
                'packageid': 1234}},
              'packageid': -1}
        """
        resp = self.send_job_and_wait(
            MsgProto(EMsg.ClientRegisterKey),
            {'key': key},
            timeout=30,
        )

        if resp:
            details = vdf.binary_loads(resp.purchase_receipt_info).get(
                'MessageObject', None)
            return EResult(resp.eresult), resp.purchase_result_details, details
        else:
            return EResult.Timeout, None, None
Beispiel #22
0
    def register_product_key(self, key):
        """Register/Redeem a CD-Key

        :param key: CD-Key
        :type  key: :class:`str`
        :return: format ``(eresult, result_details, receipt_info)``
        :rtype: :class:`tuple`

        Example ``receipt_info``:

        .. code:: python

            {'BasePrice': 0,
              'CurrencyCode': 0,
              'ErrorHeadline': '',
              'ErrorLinkText': '',
              'ErrorLinkURL': '',
              'ErrorString': '',
              'LineItemCount': 1,
              'PaymentMethod': 1,
              'PurchaseStatus': 1,
              'ResultDetail': 0,
              'Shipping': 0,
              'Tax': 0,
              'TotalDiscount': 0,
              'TransactionID': UINT_64(111111111111111111),
              'TransactionTime': 1473000000,
              'lineitems': {'0': {'ItemDescription': 'Half-Life 3',
                'TransactionID': UINT_64(11111111111111111),
                'packageid': 1234}},
              'packageid': -1}
        """
        resp = self.send_job_and_wait(MsgProto(EMsg.ClientRegisterKey),
                                      {'key': key},
                                      timeout=30,
                                      )

        if resp:
            details = vdf.binary_loads(resp.purchase_receipt_info).get('MessageObject', None)
            return EResult(resp.eresult), resp.purchase_result_details, details
        else:
            return EResult.Timeout, None, None
Beispiel #23
0
    def get_product_info(self, appids=[], packageids=[]):
        resp = self.steam.send_job_and_wait(MsgProto(EMsg.ClientPICSProductInfoRequest),
                                           {
                                               'apps': map(lambda x: {'appid': x}, appids),
                                               'packages': map(lambda x: {'packageid': x}, packageids),
                                           },
                                           timeout=10
                                           )

        if not resp: return {}

        resp = proto_to_dict(resp)

        for app in resp.get('apps', []):
            app['appinfo'] = vdf.loads(app.pop('buffer')[:-1].decode('utf-8', 'replace'))['appinfo']
            app['sha'] = hexlify(app['sha']).decode('utf-8')
        for pkg in resp.get('packages', []):
            pkg['appinfo'] = vdf.binary_loads(pkg.pop('buffer')[4:])[str(pkg['packageid'])]
            pkg['sha'] = hexlify(pkg['sha']).decode('utf-8')

        return resp
        def product_info_handler(packages, apps):
            for info in packages:
                self.package_info_handler()

                package_id = str(info.packageid)
                package_content = vdf.binary_loads(info.buffer[4:])
                package = package_content.get(package_id)
                if package is None:
                    continue

                for app in package['appids'].values():
                    appid = str(app)
                    self.app_info_handler(package_id=package_id, appid=appid)
                    apps_to_parse.append(app)

            for info in apps:
                app_content = vdf.loads(info.buffer[:-1].decode(
                    'utf-8', 'replace'))
                appid = str(app_content['appinfo']['appid'])
                try:
                    type_ = app_content['appinfo']['common']['type'].lower()
                    title = app_content['appinfo']['common']['name']
                    parent = None
                    if 'extended' in app_content['appinfo'] and type_ == 'dlc':
                        parent = app_content['appinfo']['extended'][
                            'dlcforappid']
                        logger.debug(f"Retrieved dlc {title} for {parent}")
                    if type == 'game':
                        logger.debug(f"Retrieved game {title}")
                    self.app_info_handler(appid=appid,
                                          title=title,
                                          type=type_,
                                          parent=parent)
                except KeyError:
                    logger.warning(f"Unrecognized app structure {app_content}")
                    self.app_info_handler(appid=appid,
                                          title='unknown',
                                          type='unknown',
                                          parent=None)
Beispiel #25
0
 def test_loads_unterminated_string(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x01abbbb')
Beispiel #26
0
    def get_product_info(self, apps=[], packages=[], timeout=15):
        """Get product info for apps and packages

        :param apps: items in the list should be either just ``app_id``, or :class:`dict`
        :type apps: :class:`list`
        :param packages: items in the list should be either just ``package_id``, or :class:`dict`
        :type packages: :class:`list`
        :return: dict with ``apps`` and ``packages`` containing their info, see example below
        :rtype: :class:`dict`, :class:`None`

        .. code:: python

            {'apps':     {570: {...}, ...},
             'packages': {123: {...}, ...}
            }

        Access token is needed to access full information for certain apps, and also package info.
        Each app and package has its' own access token.
        If a token is required then ``_missing_token=True`` in the response.

        App access tokens are obtained by calling :meth:`get_access_tokens`, and are returned only
        when the account has a license for the specified app. Example code:

        .. code:: python

            result = client.get_product_info(apps=[123])

            if result['apps'][123]['_missing_token']:
                tokens = client.get_access_token(apps=[123])

                result = client.get_product_info(apps=[{'appid': 123,
                                                        'access_token': tokens['apps'][123]
                                                        }])

        .. note::
            It is best to just request access token for all apps, before sending a product info
            request.

        Package tokens are located in the account license list. See :attr:`.licenses`

        .. code:: python

            result = client.get_product_info(packages=[{'packageid': 123,
                                                        'access_token': client.licenses[123].access_token,
                                                        }])
        """
        if not apps and not packages:
            return

        message = MsgProto(EMsg.ClientPICSProductInfoRequest)

        for app in apps:
                app_info = message.body.apps.add()
                app_info.only_public = False
                if isinstance(app, dict):
                        proto_fill_from_dict(app_info, app)
                else:
                        app_info.appid = app

        for package in packages:
                package_info = message.body.packages.add()
                if isinstance(package, dict):
                        proto_fill_from_dict(package_info, package)
                else:
                        package_info.packageid = package

        message.body.meta_data_only = False

        job_id = self.send_job(message)

        data = dict(apps={}, packages={})

        while True:
            chunk = self.wait_event(job_id, timeout=timeout, raises=True)

            chunk = chunk[0].body

            for app in chunk.apps:
                data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode('utf-8', 'replace'))['appinfo']
                data['apps'][app.appid]['_missing_token'] = app.missing_token
            for pkg in chunk.packages:
                data['packages'][pkg.packageid] = vdf.binary_loads(pkg.buffer[4:]).get(str(pkg.packageid), {})
                data['packages'][pkg.packageid]['_missing_token'] = pkg.missing_token

            if not chunk.response_pending:
                break

        return data
Beispiel #27
0
 def test_loads_unknown_type(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x33a\x00\x08')
Beispiel #28
0
 def test_alternative_format(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x0b\x0b')
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x08\x08', alt_format=True)
Beispiel #29
0
 def test_loads_unbalanced_nesting(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x08')
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x08\x08\x08\x08')
Beispiel #30
0
 def test_loads_unterminated_string(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x01abbbb')
Beispiel #31
0
    def get_product_info(self, apps=[], packages=[], timeout=15):
        """Get product info for apps and packages

        :param apps: items in the list should be either just ``app_id``, or :class:`dict`
        :type apps: :class:`list`
        :param packages: items in the list should be either just ``package_id``, or :class:`dict`
        :type packages: :class:`list`
        :return: dict with ``apps`` and ``packages`` containing their info, see example below
        :rtype: :class:`dict`, :class:`None`

        .. code:: python

            {'apps':     {570: {...}, ...},
             'packages': {123: {...}, ...}
            }

        When a token is needed to access the full info (e.g. branches and depots) the ``_missing_token``
        will be set to ``True``. The token can be obtained by calling :meth:`get_access_tokens` if
        the account has a license.

        .. code:: python
            result = client.get_product_info(apps=[123])

            if result['apps'][123]['_missing_token']:
                tokens = client.get_access_token(apps=[123])

                result = client.get_product_info(apps={'appid': 123,
                                                       'access_token': tokens['apps'][123]
                                                       })
        """
        if not apps and not packages:
            return

        message = MsgProto(EMsg.ClientPICSProductInfoRequest)

        for app in apps:
                app_info = message.body.apps.add()
                app_info.only_public = False
                if isinstance(app, dict):
                        proto_fill_from_dict(app_info, app)
                else:
                        app_info.appid = app

        for package in packages:
                package_info = message.body.packages.add()
                if isinstance(package, dict):
                        proto_fill_from_dict(package_info, package)
                else:
                        package_info.packageid = package

        message.body.meta_data_only = False

        job_id = self.send_job(message)

        data = dict(apps={}, packages={})

        while True:
            chunk = self.wait_event(job_id, timeout=timeout, raises=True)

            chunk = chunk[0].body

            for app in chunk.apps:
                data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode('utf-8', 'replace'))['appinfo']
                data['apps'][app.appid]['_missing_token'] = app.missing_token
            for pkg in chunk.packages:
                data['packages'][pkg.packageid] = vdf.binary_loads(pkg.buffer[4:])[str(pkg.packageid)]
                data['packages'][pkg.packageid]['_missing_token'] = pkg.missing_token

            if not chunk.response_pending:
                break

        return data
Beispiel #32
0
 def test_loads_empty(self):
     self.assertEqual(vdf.binary_loads(b''), {})
Beispiel #33
0
import vdf
import sys
import os
import json


if len(sys.argv) < 2:
    print("format: {} UserGameStatsSchema_480.bin".format(sys.argv[0]))
    exit(0)


with open(sys.argv[1], 'rb') as f:
    schema = vdf.binary_loads(f.read())

language = 'english'

STAT_TYPE_INT = '1'
STAT_TYPE_FLOAT = '2'
STAT_TYPE_AVGRATE = '3'
STAT_TYPE_BITS = '4'

achievements_out = []
stats_out = []

for appid in schema:
    sch = schema[appid]
    stat_info = sch['stats']
    for s in stat_info:
        stat = stat_info[s]
        if stat['type'] == STAT_TYPE_BITS:
            achs = stat['bits']
Beispiel #34
0
    def get_product_info(self,
                         apps=[],
                         packages=[],
                         meta_data_only=False,
                         raw=False,
                         auto_access_tokens=True,
                         timeout=15):
        """Get product info for apps and packages

        :param apps: items in the list should be either just ``app_id``, or :class:`dict`
        :type  apps: :class:`list`
        :param packages: items in the list should be either just ``package_id``, or :class:`dict`
        :type  packages: :class:`list`
        :param meta_data_only: only meta data will be returned in the reponse (e.g. change number, missing_token, sha1)
        :type  meta_data_only: :class:`bool`
        :param raw: Data buffer for each app is returned as bytes in its' original form. Apps buffer is text VDF, and package buffer is binary VDF
        :type  raw: :class:`bool`
        :param auto_access_token: automatically request and fill access tokens
        :type  auto_access_token: :class:`bool`
        :return: dict with ``apps`` and ``packages`` containing their info, see example below
        :rtype: :class:`dict`, :class:`None`

        .. code:: python

            {'apps':     {570: {...}, ...},
             'packages': {123: {...}, ...}
            }

        Access token is needed to access full information for certain apps, and also package info.
        Each app and package has its' own access token.
        If a token is required then ``_missing_token=True`` in the response.

        App access tokens are obtained by calling :meth:`get_access_tokens`, and are returned only
        when the account has a license for the specified app. Example code:

        .. code:: python

            result = client.get_product_info(apps=[123])

            if result['apps'][123]['_missing_token']:
                tokens = client.get_access_token(apps=[123])

                result = client.get_product_info(apps=[{'appid': 123,
                                                        'access_token': tokens['apps'][123]
                                                        }])

        .. note::
            It is best to just request access token for all apps, before sending a product info
            request.

        Package tokens are located in the account license list. See :attr:`.licenses`

        .. code:: python

            result = client.get_product_info(packages=[{'packageid': 123,
                                                        'access_token': client.licenses[123].access_token,
                                                        }])
        """
        if not apps and not packages:
            return

        if auto_access_tokens:
            tokens = self.get_access_tokens(
                app_ids=list(
                    map(
                        lambda app: app['appid']
                        if isinstance(app, dict) else app, apps)),
                package_ids=list(
                    map(
                        lambda pkg: pkg['packageid']
                        if isinstance(pkg, dict) else pkg, packages)))
        else:
            tokens = None

        message = MsgProto(EMsg.ClientPICSProductInfoRequest)

        for app in apps:
            app_info = message.body.apps.add()

            if tokens:
                app_info.access_token = tokens['apps'].get(
                    app['appid'] if isinstance(app, dict) else app, 0)

            if isinstance(app, dict):
                proto_fill_from_dict(app_info, app)
            else:
                app_info.appid = app

        for package in packages:
            package_info = message.body.packages.add()

            if tokens:
                package_info.access_token = tokens['packages'].get(
                    package['packageid']
                    if isinstance(package, dict) else package, 0)

            if isinstance(package, dict):
                proto_fill_from_dict(package_info, package)
            else:
                package_info.packageid = package

        message.body.meta_data_only = meta_data_only
        message.body.num_prev_failed = 0
        message.body.supports_package_tokens = 1

        job_id = self.send_job(message)

        data = dict(apps={}, packages={})

        while True:
            chunk = self.wait_event(job_id, timeout=timeout, raises=True)

            chunk = chunk[0].body

            for app in chunk.apps:
                if app.buffer and not raw:
                    data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode(
                        'utf-8', 'replace'))['appinfo']
                else:
                    data['apps'][app.appid] = {}

                data['apps'][app.appid]['_missing_token'] = app.missing_token
                data['apps'][app.appid]['_change_number'] = app.change_number
                data['apps'][app.appid]['_sha'] = hexlify(
                    app.sha).decode('ascii')
                data['apps'][app.appid]['_size'] = app.size

                if app.buffer and raw:
                    data['apps'][app.appid]['_buffer'] = app.buffer

            for pkg in chunk.packages:
                if pkg.buffer and not raw:
                    data['packages'][pkg.packageid] = vdf.binary_loads(
                        pkg.buffer[4:]).get(str(pkg.packageid), {})
                else:
                    data['packages'][pkg.packageid] = {}

                data['packages'][
                    pkg.packageid]['_missing_token'] = pkg.missing_token
                data['packages'][
                    pkg.packageid]['_change_number'] = pkg.change_number
                data['packages'][pkg.packageid]['_sha'] = hexlify(
                    pkg.sha).decode('ascii')
                data['packages'][pkg.packageid]['_size'] = pkg.size

                if pkg.buffer and raw:
                    data['packages'][pkg.packageid]['_buffer'] = pkg.buffer

            if not chunk.response_pending:
                break

        return data
Beispiel #35
0
 def test_loads_empty(self):
     self.assertEqual(vdf.binary_loads(b''), {})
Beispiel #36
0
 def test_loads_unbalanced_nesting(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x08')
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x08\x08\x08\x08')
Beispiel #37
0
    def test_merge_multiple_keys_on(self):
        # VDFDict([('a', VDFDict([('a', '1'), ('b', '2')])), ('a', VDFDict([('a', '3'), ('c', '4')]))])
        test = b'\x00a\x00\x01a\x001\x00\x01b\x002\x00\x08\x00a\x00\x01a\x003\x00\x01c\x004\x00\x08\x08'
        result = {'a': {'a': '3', 'b': '2', 'c': '4'}}

        self.assertEqual(vdf.binary_loads(test, merge_duplicate_keys=True), result)
Beispiel #38
0
 def test_loads_type_checks(self):
     with self.assertRaises(TypeError):
         vdf.binary_loads(None)
     with self.assertRaises(TypeError):
         vdf.binary_loads(b'', mapper=list)
Beispiel #39
0
def get_shortcuts(user_context):
    with open(paths.shortcuts_path(user_context), 'rb') as fp:
        vdf_dict = vdf.binary_loads(fp.read())
        return list(vdf_dict.get('shortcuts').values())
Beispiel #40
0
 def test_alternative_format(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x0b\x0b')
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x00a\x00\x00b\x00\x08\x08', alt_format=True)
Beispiel #41
0
 def test_loads_type_checks(self):
     with self.assertRaises(TypeError):
         vdf.binary_loads(None)
     with self.assertRaises(TypeError):
         vdf.binary_loads(b'', mapper=list)
Beispiel #42
0
 def test_loads_unknown_type(self):
     with self.assertRaises(SyntaxError):
         vdf.binary_loads(b'\x33a\x00\x08')